From a3ff31088e9344e0c425f82a0f17ebeb8c40a880 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sun, 1 Mar 2026 02:05:03 +0000 Subject: [PATCH 01/15] Perf Policy Group generate (#8855) * Perf Policy Group generate * Scroll to new group node * Add region group * I18n * Fix * Fix * Move default filter to Global * Default Filter List * Increase UI column widths for AddGroupServerWindow --------- Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com> --- v2rayN/ServiceLib/Global.cs | 16 +++ v2rayN/ServiceLib/Handler/ConfigHandler.cs | 134 +++++++++++++----- v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 75 ++-------- v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 33 ++--- v2rayN/ServiceLib/Resx/ResUI.fr.resx | 33 ++--- v2rayN/ServiceLib/Resx/ResUI.hu.resx | 33 ++--- v2rayN/ServiceLib/Resx/ResUI.resx | 33 ++--- v2rayN/ServiceLib/Resx/ResUI.ru.resx | 33 ++--- v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 33 ++--- v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 33 ++--- .../ViewModels/ProfilesViewModel.cs | 81 ++++------- .../Views/AddGroupServerWindow.axaml | 23 +-- .../Views/AddGroupServerWindow.axaml.cs | 3 +- .../Views/ProfilesSelectWindow.axaml | 6 +- .../v2rayN.Desktop/Views/ProfilesView.axaml | 11 +- .../Views/ProfilesView.axaml.cs | 8 +- v2rayN/v2rayN/Views/AddGroupServerWindow.xaml | 24 ++-- .../v2rayN/Views/AddGroupServerWindow.xaml.cs | 3 +- v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml | 6 +- v2rayN/v2rayN/Views/ProfilesView.xaml | 27 +--- v2rayN/v2rayN/Views/ProfilesView.xaml.cs | 8 +- 21 files changed, 272 insertions(+), 384 deletions(-) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 90721217..685b74c7 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -90,6 +90,22 @@ public class Global public const int Hysteria2DefaultHopInt = 10; + public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset"; + + public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$"; + + public static readonly List PolicyGroupDefaultFilterList = + [ + // All nodes (exclude traffic/expiry info) + PolicyGroupDefaultAllFilter, + // Low multiplier nodes, e.g. ×0.1, 0.5x, 0.1倍 + @"^.*(?:[×xX✕*]\s*0\.[0-9]+|0\.[0-9]+\s*[×xX✕*倍]).*$", + // Dedicated line nodes, e.g. IPLC, IEPL + $@"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:专线|IPLC|IEPL|中转).*$", + // Japan nodes + $@"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan).*$", + ]; + public static readonly List IEProxyProtocols = [ "{ip}:{http_port}", diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index b0bde1df..e5b6fec3 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1165,46 +1165,28 @@ public static class ConfigHandler /// /// Create a group server that combines multiple servers for load balancing - /// Generates a configuration file that references multiple servers + /// Generates a PolicyGroup profile with references to the sub-items /// /// Current configuration - /// Selected servers to combine - /// Core type to use (Xray or sing_box) - /// Load balancing algorithm + /// Sub-item for grouping /// Result object with success state and data - public static async Task AddGroupServer4Multiple(Config config, List selecteds, ECoreType coreType, EMultipleLoad multipleLoad, string? subId) + public static async Task AddGroupAllServer(Config config, SubItem? subItem) { var result = new RetResult(); - var indexId = Utils.GetGuid(false); - var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList()); + var subId = subItem?.Id; + if (subId.IsNullOrEmpty()) + { + result.Success = false; + return result; + } - var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId))?.Remarks} "; - if (coreType == ECoreType.Xray) - { - remark += multipleLoad switch - { - EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerXrayLeastPing, - EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerXrayFallback, - EMultipleLoad.Random => ResUI.menuGenGroupMultipleServerXrayRandom, - EMultipleLoad.RoundRobin => ResUI.menuGenGroupMultipleServerXrayRoundRobin, - EMultipleLoad.LeastLoad => ResUI.menuGenGroupMultipleServerXrayLeastLoad, - _ => ResUI.menuGenGroupMultipleServerXrayRoundRobin, - }; - } - else if (coreType == ECoreType.sing_box) - { - remark += multipleLoad switch - { - EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerSingBoxLeastPing, - EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerSingBoxFallback, - _ => ResUI.menuGenGroupMultipleServerSingBoxLeastPing, - }; - } + var indexId = Utils.GetGuid(false); + var remark = $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup}"; var profile = new ProfileItem { IndexId = indexId, - CoreType = coreType, + CoreType = ECoreType.Xray, ConfigType = EConfigType.PolicyGroup, Remarks = remark, IsSub = false @@ -1215,8 +1197,10 @@ public static class ConfigHandler } var extraItem = new ProtocolExtraItem { - ChildItems = childProfileIndexId, - MultipleLoad = multipleLoad, + MultipleLoad = EMultipleLoad.LeastPing, + GroupType = profile.ConfigType.ToString(), + SubChildItems = subId, + Filter = Global.PolicyGroupDefaultAllFilter, }; profile.SetProtocolExtra(extraItem); var ret = await AddServerCommon(config, profile, true); @@ -1225,6 +1209,92 @@ public static class ConfigHandler return result; } + private static string CombineWithDefaultAllFilter(string regionPattern) + { + return $"^(?!.*(?:{Global.PolicyGroupExcludeKeywords})).*(?:{regionPattern}).*$"; + } + + private static readonly Dictionary PolicyGroupRegionFilters = new() + { + { "JP", "日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan" }, + { "US", "美国|\\b[Uu][Ss]\\b|🇺🇸|[Uu]nited [Ss]tates|\\b[Uu][Ss][Aa]\\b" }, + { "HK", "香港|\\b[Hh][Kk]\\b|🇭🇰|[Hh]ong ?[Kk]ong" }, + { "TW", "台湾|台灣|\\b[Tt][Ww]\\b|🇹🇼|[Tt]aiwan" }, + { "KR", "韩国|\\b[Kk][Rr]\\b|🇰🇷|[Kk]orea" }, + { "SG", "新加坡|\\b[Ss][Gg]\\b|🇸🇬|[Ss]ingapore" }, + { "DE", "德国|\\b[Dd][Ee]\\b|🇩🇪|[Gg]ermany" }, + { "FR", "法国|\\b[Ff][Rr]\\b|🇫🇷|[Ff]rance" }, + { "GB", "英国|\\b[Gg][Bb]\\b|🇬🇧|[Uu]nited [Kk]ingdom|[Bb]ritain" }, + { "CA", "加拿大|🇨🇦|[Cc]anada" }, + { "AU", "澳大利亚|\\b[Aa][Uu]\\b|🇦🇺|[Aa]ustralia" }, + { "RU", "俄罗斯|\\b[Rr][Uu]\\b|🇷🇺|[Rr]ussia" }, + { "BR", "巴西|\\b[Bb][Rr]\\b|🇧🇷|[Bb]razil" }, + { "IN", "印度|🇮🇳|[Ii]ndia" }, + { "VN", "越南|\\b[Vv][Nn]\\b|🇻🇳|[Vv]ietnam" }, + { "ID", "印度尼西亚|\\b[Ii][Dd]\\b|🇮🇩|[Ii]ndonesia" }, + { "MX", "墨西哥|\\b[Mm][Xx]\\b|🇲🇽|[Mm]exico" } + }; + + public static async Task AddGroupRegionServer(Config config, SubItem? subItem) + { + var result = new RetResult(); + var subId = subItem?.Id; + if (subId.IsNullOrEmpty()) + { + result.Success = false; + return result; + } + var childProfiles = await AppManager.Instance.ProfileItems(subId); + List indexIdList = []; + + foreach (var regionFilter in PolicyGroupRegionFilters) + { + var indexId = Utils.GetGuid(false); + var remark = $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup} - {regionFilter.Key}"; + var profile = new ProfileItem + { + IndexId = indexId, + CoreType = ECoreType.Xray, + ConfigType = EConfigType.PolicyGroup, + Remarks = remark, + IsSub = false + }; + if (!subId.IsNullOrEmpty()) + { + profile.Subid = subId; + } + var extraItem = new ProtocolExtraItem + { + MultipleLoad = EMultipleLoad.LeastPing, + GroupType = profile.ConfigType.ToString(), + SubChildItems = subId, + Filter = CombineWithDefaultAllFilter(regionFilter.Value), + }; + profile.SetProtocolExtra(extraItem); + + var matchedChildProfiles = childProfiles?.Where(p => + p != null && + p.IsValid() && + !p.ConfigType.IsComplexType() && + (extraItem.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extraItem.Filter)) + ) + .ToList() ?? []; + if (matchedChildProfiles.Count == 0) + { + continue; + } + + var ret = await AddServerCommon(config, profile, true); + if (ret == 0) + { + indexIdList.Add(indexId); + } + } + result.Success = indexIdList.Count > 0; + result.Data = indexIdList; + return result; + } + /// /// Get a SOCKS server profile for pre-SOCKS functionality /// Used when TUN mode is enabled or when a custom config has a pre-SOCKS port diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 5e35d3bb..b00bec2d 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -834,6 +834,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 All configurations 的本地化字符串。 + /// + public static string menuAllServers { + get { + return ResourceManager.GetString("menuAllServers", resourceCulture); + } + } + /// /// 查找类似 Backup and Restore 的本地化字符串。 /// @@ -1006,74 +1015,20 @@ namespace ServiceLib.Resx { } /// - /// 查找类似 Generate Policy Group from Multiple Profiles 的本地化字符串。 + /// 查找类似 Generate Policy Group 的本地化字符串。 /// - public static string menuGenGroupMultipleServer { + public static string menuGenGroupServer { get { - return ResourceManager.GetString("menuGenGroupMultipleServer", resourceCulture); + return ResourceManager.GetString("menuGenGroupServer", resourceCulture); } } /// - /// 查找类似 Fallback by sing-box 的本地化字符串。 + /// 查找类似 Group by Region 的本地化字符串。 /// - public static string menuGenGroupMultipleServerSingBoxFallback { + public static string menuGenRegionGroup { get { - return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxFallback", resourceCulture); - } - } - - /// - /// 查找类似 LeastPing by sing-box 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerSingBoxLeastPing { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxLeastPing", resourceCulture); - } - } - - /// - /// 查找类似 Fallback by Xray 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerXrayFallback { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerXrayFallback", resourceCulture); - } - } - - /// - /// 查找类似 LeastLoad by Xray 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerXrayLeastLoad { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastLoad", resourceCulture); - } - } - - /// - /// 查找类似 LeastPing by Xray 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerXrayLeastPing { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastPing", resourceCulture); - } - } - - /// - /// 查找类似 Random by Xray 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerXrayRandom { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerXrayRandom", resourceCulture); - } - } - - /// - /// 查找类似 RoundRobin by Xray 的本地化字符串。 - /// - public static string menuGenGroupMultipleServerXrayRoundRobin { - get { - return ResourceManager.GetString("menuGenGroupMultipleServerXrayRoundRobin", resourceCulture); + return ResourceManager.GetString("menuGenRegionGroup", resourceCulture); } } diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 0df75d77..16836861 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1371,24 +1371,6 @@ مخفی و پورت می شود، با کاما (،) جدا می شود - - Generate Policy Group from Multiple Profiles - - - چند سرور تصادفی توسط Xray - - - چند سرور RoundRobin توسط Xray - - - چند سرور LeastPing توسط Xray - - - چند سرور LeastLoad توسط Xray - - - LeastPing چند سرور توسط sing-box - صادر کردن سرور @@ -1533,12 +1515,6 @@ Fallback - - Multi-Configuration Fallback by sing-box - - - Multi-Configuration Fallback by Xray - Core '{0}' does not support network type '{1}' @@ -1686,4 +1662,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Subscription next proxy {0} not found. Skipping. + + Generate Policy Group + + + All configurations + + + Group by Region + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index 6c882112..253831ef 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1368,24 +1368,6 @@ Écrase le port ; pour plusieurs groupes, séparer par virgules (,) - - Générer un groupe de stratégie depuis plusieurs profils - - - Xray aléatoire (multi-sélection) - - - Xray équilibrage (tourniquet) multi-sélection - - - Xray latence minimale (multi-sélection) - - - Xray le plus stable (multi-sélection) - - - sing-box latence minimale (multi-sélection) - Exporter @@ -1530,12 +1512,6 @@ Basculement (failover) - - sing-box basculement (multi-sélection) - - - Xray basculement (multi-sélection) - Le cœur « {0} » ne prend pas en charge le type de réseau « {1} » @@ -1683,4 +1659,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Subscription next proxy {0} not found. Skipping. + + Generate Policy Group + + + All configurations + + + Group by Region + diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index dd7d22f4..a29d8987 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1371,24 +1371,6 @@ A portot lefedi, vesszővel (,) elválasztva - - Generate Policy Group from Multiple Profiles - - - Több konfiguráció véletlenszerűen Xray szerint - - - Több konfiguráció RoundRobin Xray szerint - - - Több konfiguráció legkisebb pinggel Xray szerint - - - Több konfiguráció legkisebb terheléssel Xray szerint - - - Több konfiguráció legkisebb pinggel sing-box szerint - Konfiguráció exportálása @@ -1533,12 +1515,6 @@ Fallback - - Multi-Configuration Fallback by sing-box - - - Multi-Configuration Fallback by Xray - Core '{0}' does not support network type '{1}' @@ -1686,4 +1662,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Subscription next proxy {0} not found. Skipping. + + Generate Policy Group + + + All configurations + + + Group by Region + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index fa53ccc8..17416125 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1371,24 +1371,6 @@ Will cover the port, separate with commas (,) - - Generate Policy Group from Multiple Profiles - - - Random by Xray - - - RoundRobin by Xray - - - LeastPing by Xray - - - LeastLoad by Xray - - - LeastPing by sing-box - Export @@ -1533,12 +1515,6 @@ Fallback - - Fallback by sing-box - - - Fallback by Xray - Core '{0}' does not support network type '{1}' @@ -1686,4 +1662,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Subscription next proxy {0} not found. Skipping. + + Generate Policy Group + + + All configurations + + + Group by Region + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index b33a10e3..d8a174d5 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1371,24 +1371,6 @@ Заменит указанный порт, перечисляйте через запятую (,) - - Generate Policy Group from Multiple Profiles - - - Случайный (Xray) - - - Круговой (Xray) - - - Минимальное RTT (минимальное время туда-обратно) (Xray) - - - Минимальная нагрузка (Xray) - - - Минимальное RTT (минимальное время туда-обратно) (sing-box) - Экспортировать конфигурацию @@ -1533,12 +1515,6 @@ Fallback - - Multi-Configuration Fallback by sing-box - - - Multi-Configuration Fallback by Xray - Core '{0}' does not support network type '{1}' @@ -1686,4 +1662,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Subscription next proxy {0} not found. Skipping. + + Generate Policy Group + + + All configurations + + + Group by Region + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index be76525e..3a4e4924 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1368,24 +1368,6 @@ 会覆盖端口,多组时用逗号 (,) 隔开 - - 多选生成策略组 - - - 多选随机 Xray - - - 多选负载均衡 Xray - - - 多选最低延迟 Xray - - - 多选最稳定 Xray - - - 多选最低延迟 sing-box - 导出 @@ -1530,12 +1512,6 @@ 故障转移 - - 多选故障转移 sing-box - - - 多选故障转移 Xray - 核心 '{0}' 不支持网络类型 '{1}' @@ -1683,4 +1659,13 @@ 订阅后置节点 {0} 未找到,已跳过。 + + 一键生成策略组 + + + 全部配置项 + + + 按地区分组 + \ 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 4132a27b..330a36e2 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1368,24 +1368,6 @@ 會覆蓋埠,多組時用逗號 (,) 隔開 - - 多選生成策略組 - - - 多選隨機 Xray - - - 多選負載平衡 Xray - - - 多選最低延遲 Xray - - - 多選最穩定 Xray - - - 多選最低延遲 sing-box - 匯出 @@ -1530,12 +1512,6 @@ 容錯移轉 - - 多選容錯移轉 sing-box - - - 多選容錯移轉 Xray - 核心 '{0}' 不支援網路類型 '{1}' @@ -1683,4 +1659,13 @@ Subscription next proxy {0} not found. Skipping. + + Generate Policy Group + + + All configurations + + + Group by Region + \ No newline at end of file diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 1cc6f46e..91c745d6 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -8,6 +8,7 @@ public class ProfilesViewModel : MyReactiveObject private string _serverFilter = string.Empty; private Dictionary _dicHeaderSort = new(); private SpeedtestService? _speedtestService; + private string? _pendingSelectIndexId; #endregion private prop @@ -43,13 +44,8 @@ public class ProfilesViewModel : MyReactiveObject public ReactiveCommand CopyServerCmd { get; } public ReactiveCommand SetDefaultServerCmd { get; } public ReactiveCommand ShareServerCmd { get; } - public ReactiveCommand GenGroupMultipleServerXrayRandomCmd { get; } - public ReactiveCommand GenGroupMultipleServerXrayRoundRobinCmd { get; } - public ReactiveCommand GenGroupMultipleServerXrayLeastPingCmd { get; } - public ReactiveCommand GenGroupMultipleServerXrayLeastLoadCmd { get; } - public ReactiveCommand GenGroupMultipleServerXrayFallbackCmd { get; } - public ReactiveCommand GenGroupMultipleServerSingBoxLeastPingCmd { get; } - public ReactiveCommand GenGroupMultipleServerSingBoxFallbackCmd { get; } + public ReactiveCommand GenGroupAllServerCmd { get; } + public ReactiveCommand GenGroupRegionServerCmd { get; } //servers move public ReactiveCommand MoveTopCmd { get; } @@ -134,33 +130,13 @@ public class ProfilesViewModel : MyReactiveObject { await ShareServerAsync(); }, canEditRemove); - GenGroupMultipleServerXrayRandomCmd = ReactiveCommand.CreateFromTask(async () => + GenGroupAllServerCmd = ReactiveCommand.CreateFromTask(async () => { - await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Random); + await GenGroupAllServer(); }, canEditRemove); - GenGroupMultipleServerXrayRoundRobinCmd = ReactiveCommand.CreateFromTask(async () => + GenGroupRegionServerCmd = ReactiveCommand.CreateFromTask(async () => { - await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.RoundRobin); - }, canEditRemove); - GenGroupMultipleServerXrayLeastPingCmd = ReactiveCommand.CreateFromTask(async () => - { - await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastPing); - }, canEditRemove); - GenGroupMultipleServerXrayLeastLoadCmd = ReactiveCommand.CreateFromTask(async () => - { - await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad); - }, canEditRemove); - GenGroupMultipleServerXrayFallbackCmd = ReactiveCommand.CreateFromTask(async () => - { - await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Fallback); - }, canEditRemove); - GenGroupMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () => - { - await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing); - }, canEditRemove); - GenGroupMultipleServerSingBoxFallbackCmd = ReactiveCommand.CreateFromTask(async () => - { - await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.Fallback); + await GenGroupRegionServer(); }, canEditRemove); //servers move @@ -392,15 +368,14 @@ public class ProfilesViewModel : MyReactiveObject ProfileItems.AddRange(lstModel); if (lstModel.Count > 0) { - var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); - if (selected != null) + ProfileItemModel? selected = null; + if (!_pendingSelectIndexId.IsNullOrEmpty()) { - SelectedProfile = selected; - } - else - { - SelectedProfile = lstModel.First(); + selected = lstModel.FirstOrDefault(t => t.IndexId == _pendingSelectIndexId); + _pendingSelectIndexId = null; } + selected ??= lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); + SelectedProfile = selected ?? lstModel.First(); } await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null); @@ -641,29 +616,29 @@ public class ProfilesViewModel : MyReactiveObject await _updateView?.Invoke(EViewAction.ShareServer, url); } - private async Task GenGroupMultipleServer(ECoreType coreType, EMultipleLoad multipleLoad) + private async Task GenGroupAllServer() { - var lstSelected = await GetProfileItems(true); - if (lstSelected == null) - { - return; - } - - var ret = await ConfigHandler.AddGroupServer4Multiple(_config, lstSelected, coreType, multipleLoad, SelectedSub?.Id); + var ret = await ConfigHandler.AddGroupAllServer(_config, SelectedSub); if (ret.Success != true) { NoticeManager.Instance.Enqueue(ResUI.OperationFailed); return; } - if (ret?.Data?.ToString() == _config.IndexId) + _pendingSelectIndexId = ret.Data?.ToString(); + await RefreshServers(); + } + + private async Task GenGroupRegionServer() + { + var ret = await ConfigHandler.AddGroupRegionServer(_config, SelectedSub); + if (ret.Success != true) { - await RefreshServers(); - Reload(); - } - else - { - await SetDefaultServer(ret?.Data?.ToString()); + NoticeManager.Instance.Enqueue(ResUI.OperationFailed); + return; } + var indexIdList = ret.Data as List; + _pendingSelectIndexId = indexIdList?.FirstOrDefault(); + await RefreshServers(); } public async Task SortServer(string colName) diff --git a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml index c036bb03..e50da940 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml @@ -95,7 +95,7 @@ @@ -117,7 +117,8 @@ Grid.Column="2" Margin="{StaticResource Margin4}" VerticalAlignment="Center" - Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" /> + Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" + TextWrapping="Wrap" /> - + VerticalAlignment="Center" + IsEditable="True" /> @@ -184,11 +187,11 @@ Binding="{Binding ConfigType}" Header="{x:Static resx:ResUI.LvServiceType}" /> ResUI.TbRoundRobin, ResUI.TbLeastLoad, }; + cmbFilter.ItemsSource = Global.PolicyGroupDefaultFilterList; switch (profileItem.ConfigType) { @@ -53,7 +54,7 @@ public partial class AddGroupServerWindow : WindowBase this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).DisposeWith(disposables); //this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Filter, v => v.cmbFilter.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables); diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml b/v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml index c89a7f71..2dd44b54 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml @@ -90,17 +90,17 @@ Header="{x:Static resx:ResUI.LvServiceType}" Tag="ConfigType" /> diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml index 116f35fa..e0751ca1 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml @@ -193,14 +193,9 @@ - - - - - - - - + + + diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs index aaebb518..4ca8e793 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs @@ -60,12 +60,8 @@ public partial class ProfilesView : ReactiveUserControl this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRandomCmd, v => v.menuGenGroupMultipleServerXrayRandom).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRoundRobinCmd, v => v.menuGenGroupMultipleServerXrayRoundRobin).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxFallbackCmd, v => v.menuGenGroupMultipleServerSingBoxFallback).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.GenGroupAllServerCmd, v => v.menuGenGroupAllServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.GenGroupRegionServerCmd, v => v.menuGenGroupRegionServer).DisposeWith(disposables); //servers move //this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml index baaa0114..39acbaec 100644 --- a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml @@ -147,7 +147,7 @@ - + @@ -163,7 +163,7 @@ x:Name="cmbSubChildItems" Grid.Row="0" Grid.Column="1" - Width="200" + Width="600" Margin="{StaticResource Margin4}" DisplayMemberPath="Remarks" Style="{StaticResource DefComboBox}" /> @@ -173,7 +173,8 @@ Margin="{StaticResource Margin4}" VerticalAlignment="Center" Style="{StaticResource ToolbarTextBlock}" - Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" /> + Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" + TextWrapping="Wrap" /> - + IsEditable="True" + Style="{StaticResource DefComboBox}" /> @@ -251,11 +253,11 @@ Binding="{Binding ConfigType}" Header="{x:Static resx:ResUI.LvServiceType}" /> vm.PolicyGroupType, v => v.cmbPolicyGroupType.Text).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Filter, v => v.cmbFilter.Text).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.ChildItemsObs, v => v.lstChild.ItemsSource).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml b/v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml index 453d8c03..a05580c4 100644 --- a/v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml +++ b/v2rayN/v2rayN/Views/ProfilesSelectWindow.xaml @@ -119,17 +119,17 @@ ExName="ConfigType" Header="{x:Static resx:ResUI.LvServiceType}" /> diff --git a/v2rayN/v2rayN/Views/ProfilesView.xaml b/v2rayN/v2rayN/Views/ProfilesView.xaml index 6cf0d159..fd984306 100644 --- a/v2rayN/v2rayN/Views/ProfilesView.xaml +++ b/v2rayN/v2rayN/Views/ProfilesView.xaml @@ -240,32 +240,15 @@ Header="{x:Static resx:ResUI.menuExport2ShareUrlBase64}" /> - + + Header="{x:Static resx:ResUI.menuAllServers}" /> - - - - - + Header="{x:Static resx:ResUI.menuGenRegionGroup}" /> diff --git a/v2rayN/v2rayN/Views/ProfilesView.xaml.cs b/v2rayN/v2rayN/Views/ProfilesView.xaml.cs index 58b468a4..46def3e2 100644 --- a/v2rayN/v2rayN/Views/ProfilesView.xaml.cs +++ b/v2rayN/v2rayN/Views/ProfilesView.xaml.cs @@ -54,12 +54,8 @@ public partial class ProfilesView this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRandomCmd, v => v.menuGenGroupMultipleServerXrayRandom).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRoundRobinCmd, v => v.menuGenGroupMultipleServerXrayRoundRobin).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxFallbackCmd, v => v.menuGenGroupMultipleServerSingBoxFallback).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.GenGroupAllServerCmd, v => v.menuGenGroupAllServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.GenGroupRegionServerCmd, v => v.menuGenGroupRegionServer).DisposeWith(disposables); //servers move this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); From 6b4ae5a386463bb17a6cd812c71bae6198ccb7a1 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sun, 1 Mar 2026 05:47:48 +0000 Subject: [PATCH 02/15] Fix (#8864) * Fix * Build all contexts * Notify validator result * Fix --- .../Builder/CoreConfigContextBuilder.cs | 106 ++++++++++++++++++ v2rayN/ServiceLib/Handler/ConfigHandler.cs | 41 ------- v2rayN/ServiceLib/Manager/CoreManager.cs | 22 ++-- v2rayN/ServiceLib/Manager/NoticeManager.cs | 21 ++++ .../ViewModels/MainWindowViewModel.cs | 21 +--- .../ViewModels/ProfilesViewModel.cs | 26 +---- 6 files changed, 144 insertions(+), 93 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs index 23a495f8..4894b334 100644 --- a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs +++ b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs @@ -5,6 +5,39 @@ public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeVali public bool Success => ValidatorResult.Success; } +/// +/// Holds the results of a full context build, including the main context and an optional +/// pre-socks context (e.g. for TUN protection or pre-socks chaining). +/// +public record CoreConfigContextBuilderAllResult( + CoreConfigContextBuilderResult MainResult, + CoreConfigContextBuilderResult? PreSocksResult) +{ + /// True only when both the main result and (if present) the pre-socks result succeeded. + public bool Success => MainResult.Success && (PreSocksResult?.Success ?? true); + + /// + /// Merges all errors and warnings from the main result and the optional pre-socks result + /// into a single for unified notification. + /// + public NodeValidatorResult CombinedValidatorResult => new( + [.. MainResult.ValidatorResult.Errors, .. PreSocksResult?.ValidatorResult.Errors ?? []], + [.. MainResult.ValidatorResult.Warnings, .. PreSocksResult?.ValidatorResult.Warnings ?? []]); + + /// + /// The main context with TunProtectSsPort/ProxyRelaySsPort and ProtectDomainList merged in + /// from the pre-socks result (if any). Pass this to the core runner. + /// + public CoreConfigContext ResolvedMainContext => PreSocksResult is not null + ? MainResult.Context with + { + TunProtectSsPort = PreSocksResult.Context.TunProtectSsPort, + ProxyRelaySsPort = PreSocksResult.Context.ProxyRelaySsPort, + ProtectDomainList = [.. MainResult.Context.ProtectDomainList ?? [], .. PreSocksResult.Context.ProtectDomainList ?? []], + } + : MainResult.Context; +} + public class CoreConfigContextBuilder { /// @@ -75,6 +108,79 @@ public class CoreConfigContextBuilder return new CoreConfigContextBuilderResult(context, validatorResult); } + /// + /// Builds the main for and, when + /// the main build succeeds, also builds the optional pre-socks context required for TUN + /// protection or pre-socks proxy chaining. + /// + public static async Task BuildAll(Config config, ProfileItem node) + { + var mainResult = await Build(config, node); + if (!mainResult.Success) + { + return new CoreConfigContextBuilderAllResult(mainResult, null); + } + + var preResult = await BuildPreSocksIfNeeded(mainResult.Context); + return new CoreConfigContextBuilderAllResult(mainResult, preResult); + } + + /// + /// Determines whether a pre-socks context is required for + /// and, if so, builds and returns it. Returns null when no pre-socks core is needed. + /// + private static async Task BuildPreSocksIfNeeded(CoreConfigContext nodeContext) + { + var config = nodeContext.AppConfig; + var node = nodeContext.Node; + var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); + + var preSocksItem = ConfigHandler.GetPreSocksItem(config, node, coreType); + if (preSocksItem != null) + { + var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem); + return preSocksResult with + { + Context = preSocksResult.Context with + { + ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []], + } + }; + } + + if (!nodeContext.IsTunEnabled + || coreType != ECoreType.Xray + || node.ConfigType == EConfigType.Custom) + { + return null; + } + + var tunProtectSsPort = Utils.GetFreePort(); + var proxyRelaySsPort = Utils.GetFreePort(); + var preItem = new ProfileItem() + { + CoreType = ECoreType.sing_box, + ConfigType = EConfigType.Shadowsocks, + Address = Global.Loopback, + Port = proxyRelaySsPort, + Password = Global.None, + }; + preItem.SetProtocolExtra(preItem.GetProtocolExtra() with + { + SsMethod = Global.None, + }); + var preResult2 = await Build(nodeContext.AppConfig, preItem); + return preResult2 with + { + Context = preResult2.Context with + { + ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preResult2.Context.ProtectDomainList ?? []], + TunProtectSsPort = tunProtectSsPort, + ProxyRelaySsPort = proxyRelaySsPort, + } + }; + } + /// /// Resolves a node into the context, optionally wrapping it in a subscription-level proxy chain. /// Returns the effective (possibly replaced) node and the validation result. diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index e5b6fec3..f3ef7bce 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1321,47 +1321,6 @@ public static class ConfigHandler return itemSocks; } - public static CoreConfigContext? GetPreSocksCoreConfigContext(CoreConfigContext nodeContext) - { - var config = nodeContext.AppConfig; - var node = nodeContext.Node; - var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); - - var preSocksItem = GetPreSocksItem(config, node, coreType); - if (preSocksItem != null) - { - return nodeContext with { Node = preSocksItem, }; - } - - if ((!nodeContext.IsTunEnabled) - || coreType != ECoreType.Xray - || node.ConfigType == EConfigType.Custom) - { - return null; - } - var tunProtectSsPort = Utils.GetFreePort(); - var proxyRelaySsPort = Utils.GetFreePort(); - var preItem = new ProfileItem() - { - CoreType = ECoreType.sing_box, - ConfigType = EConfigType.Shadowsocks, - Address = Global.Loopback, - Port = proxyRelaySsPort, - Password = Global.None, - }; - preItem.SetProtocolExtra(preItem.GetProtocolExtra() with - { - SsMethod = Global.None, - }); - var preContext = nodeContext with - { - Node = preItem, - TunProtectSsPort = tunProtectSsPort, - ProxyRelaySsPort = proxyRelaySsPort, - }; - return preContext; - } - /// /// Remove servers with invalid test results (timeout) /// Useful for cleaning up subscription lists diff --git a/v2rayN/ServiceLib/Manager/CoreManager.cs b/v2rayN/ServiceLib/Manager/CoreManager.cs index cd30450f..b2df4562 100644 --- a/v2rayN/ServiceLib/Manager/CoreManager.cs +++ b/v2rayN/ServiceLib/Manager/CoreManager.cs @@ -57,27 +57,19 @@ public class CoreManager } } - public async Task LoadCore(CoreConfigContext? context) + /// Resolved main context (with pre-socks ports already merged if applicable). + /// Optional pre-socks context passed to . + public async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext) { - if (context == null) + if (mainContext == null) { await UpdateFunc(false, ResUI.CheckServerSettings); return; } - var contextMod = context; - var node = contextMod.Node; + var node = mainContext.Node; var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); - var preContext = ConfigHandler.GetPreSocksCoreConfigContext(contextMod); - if (preContext is not null) - { - contextMod = contextMod with - { - TunProtectSsPort = preContext.TunProtectSsPort, - ProxyRelaySsPort = preContext.ProxyRelaySsPort, - }; - } - var result = await CoreConfigHandler.GenerateClientConfig(contextMod, fileName); + var result = await CoreConfigHandler.GenerateClientConfig(mainContext, fileName); if (result.Success != true) { await UpdateFunc(true, result.Msg); @@ -96,7 +88,7 @@ public class CoreManager await WindowsUtils.RemoveTunDevice(); } - await CoreStart(contextMod); + await CoreStart(mainContext); await CoreStartPreService(preContext); if (_processService != null) { diff --git a/v2rayN/ServiceLib/Manager/NoticeManager.cs b/v2rayN/ServiceLib/Manager/NoticeManager.cs index da034f02..184ef79c 100644 --- a/v2rayN/ServiceLib/Manager/NoticeManager.cs +++ b/v2rayN/ServiceLib/Manager/NoticeManager.cs @@ -38,4 +38,25 @@ public class NoticeManager Enqueue(msg); SendMessage(msg); } + + /// + /// Sends each error and warning in to the message panel + /// and enqueues a summary snack notification (capped at 10 messages). + /// Returns true when there were any messages so the caller can decide on early-return + /// based on . + /// + public bool NotifyValidatorResult(NodeValidatorResult validatorResult) + { + var msgs = new List([.. validatorResult.Errors, .. validatorResult.Warnings]); + if (msgs.Count == 0) + { + return false; + } + foreach (var msg in msgs) + { + SendMessage(msg); + } + Enqueue(Utils.List2String(msgs.Take(10).ToList(), true)); + return true; + } } diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index becabf25..0bd0c588 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -546,24 +546,15 @@ public class MainWindowViewModel : MyReactiveObject NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings); return; } - var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, profileItem); - var msgs = new List([..validatorResult.Errors, ..validatorResult.Warnings]); - if (msgs.Count > 0) + var allResult = await CoreConfigContextBuilder.BuildAll(_config, profileItem); + if (NoticeManager.Instance.NotifyValidatorResult(allResult.CombinedValidatorResult) && !allResult.Success) { - foreach (var msg in msgs) - { - NoticeManager.Instance.SendMessage(msg); - } - NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true)); - if (!validatorResult.Success) - { - return; - } + return; } await Task.Run(async () => { - await LoadCore(context); + await LoadCore(allResult.ResolvedMainContext, allResult.PreSocksResult?.Context); await SysProxyHandler.UpdateSysProxy(_config, false); await Task.Delay(1000); }); @@ -604,9 +595,9 @@ public class MainWindowViewModel : MyReactiveObject RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled); } - private async Task LoadCore(CoreConfigContext? context) + private async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext) { - await CoreManager.Instance.LoadCore(context); + await CoreManager.Instance.LoadCore(mainContext, preContext); } #endregion core job diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 91c745d6..2b0de98d 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -764,18 +764,9 @@ public class ProfilesViewModel : MyReactiveObject } var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item); - var msgs = new List([..validatorResult.Errors, ..validatorResult.Warnings]); - if (msgs.Count > 0) + if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success) { - foreach (var msg in msgs) - { - NoticeManager.Instance.SendMessage(msg); - } - NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true)); - if (!validatorResult.Success) - { - return; - } + return; } if (blClipboard) @@ -804,18 +795,9 @@ public class ProfilesViewModel : MyReactiveObject return; } var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item); - var msgs = new List([..validatorResult.Errors, ..validatorResult.Warnings]); - if (msgs.Count > 0) + if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success) { - foreach (var msg in msgs) - { - NoticeManager.Instance.SendMessage(msg); - } - NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true)); - if (!validatorResult.Success) - { - return; - } + return; } var result = await CoreConfigHandler.GenerateClientConfig(context, fileName); if (result.Success != true) From d56e896f0721fc1f8a6b54d4178bf1afdfbe6dce Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:20:05 +0800 Subject: [PATCH 03/15] Ignore json file extensions in IsDomain --- v2rayN/ServiceLib/Common/Utils.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/v2rayN/ServiceLib/Common/Utils.cs b/v2rayN/ServiceLib/Common/Utils.cs index 75ef4627..3907b1a7 100644 --- a/v2rayN/ServiceLib/Common/Utils.cs +++ b/v2rayN/ServiceLib/Common/Utils.cs @@ -497,6 +497,13 @@ public class Utils return false; } + var ext = Path.GetExtension(domain); + if (ext.IsNotEmpty() + && ext[1..].ToLowerInvariant() is "json" or "txt" or "xml" or "cfg" or "ini" or "log" or "yaml" or "yml" or "toml") + { + return false; + } + return Uri.CheckHostName(domain) == UriHostNameType.Dns; } From 99d67ca3f1b08be1dec70b1edc3ba8561f4b6c2f Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sun, 1 Mar 2026 06:41:02 +0000 Subject: [PATCH 04/15] Fix DNS routing (#8841) --- v2rayN/ServiceLib/Global.cs | 1 + v2rayN/ServiceLib/Models/V2rayConfig.cs | 6 -- .../CoreConfig/V2ray/V2rayDnsService.cs | 64 +++++++++++++++---- 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 685b74c7..5478cd2a 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -49,6 +49,7 @@ public class Global public const string DirectTag = "direct"; public const string BlockTag = "block"; public const string DnsTag = "dns-module"; + public const string DirectDnsTag = "direct-dns"; public const string BalancerTagSuffix = "-round"; public const string StreamSecurity = "tls"; public const string StreamSecurityReality = "reality"; diff --git a/v2rayN/ServiceLib/Models/V2rayConfig.cs b/v2rayN/ServiceLib/Models/V2rayConfig.cs index 80baf43b..2393d5fb 100644 --- a/v2rayN/ServiceLib/Models/V2rayConfig.cs +++ b/v2rayN/ServiceLib/Models/V2rayConfig.cs @@ -220,12 +220,6 @@ public class DnsServer4Ray public List? domains { get; set; } public bool? skipFallback { get; set; } public List? expectedIPs { get; set; } - public List? unexpectedIPs { get; set; } - public string? clientIp { get; set; } - public string? queryStrategy { get; set; } - public int? timeoutMs { get; set; } - public bool? disableCache { get; set; } - public bool? finalQuery { get; set; } public string? tag { get; set; } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs index 1d31f7b2..021fee2f 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs @@ -71,6 +71,25 @@ public partial class CoreConfigV2rayService dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null; // DNS routing + var directDnsTags = dnsItem.servers + .Select(server => + { + var tagNode = (server as JsonObject)?["tag"]; + return tagNode is JsonValue value && value.TryGetValue(out var tag) ? tag : null; + }) + .Where(tag => tag is not null && tag.StartsWith(Global.DirectDnsTag, StringComparison.Ordinal)) + .Select(tag => tag!) + .ToList(); + if (directDnsTags.Count > 0) + { + _coreConfig.routing.rules.Add(new() + { + type = "field", + inboundTag = directDnsTags, + outboundTag = Global.DirectTag, + }); + } + var finalRule = BuildFinalRule(); dnsItem.tag = Global.DnsTag; _coreConfig.routing.rules.Add(new() @@ -78,7 +97,7 @@ public partial class CoreConfigV2rayService type = "field", inboundTag = [Global.DnsTag], outboundTag = finalRule.outboundTag, - balancerTag = finalRule.balancerTag + balancerTag = finalRule.balancerTag, }); _coreConfig.dns = dnsItem; @@ -212,11 +231,13 @@ public partial class CoreConfigV2rayService dnsItem.servers ??= []; + var directDnsTagIndex = 1; + AddDnsServers(remoteDNSAddress, proxyDomainList); - AddDnsServers(directDNSAddress, directDomainList); + AddDnsServers(directDNSAddress, directDomainList, true); AddDnsServers(remoteDNSAddress, proxyGeositeList); - AddDnsServers(directDNSAddress, directGeositeList); - AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs); + AddDnsServers(directDNSAddress, directGeositeList, true); + AddDnsServers(directDNSAddress, expectedDomainList, true, expectedIPs); if (dnsServerDomains.Count > 0) { AddDnsServers(bootstrapDNSAddress, dnsServerDomains); @@ -234,8 +255,21 @@ public partial class CoreConfigV2rayService useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork; } - var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress; - dnsItem.servers.AddRange(defaultDnsServers); + if (!useDirectDns) + { + dnsItem.servers.AddRange(remoteDNSAddress); + } + else + { + foreach (var dns in directDNSAddress) + { + var dnsServer = CreateDnsServer(dns, []); + dnsServer.tag = $"{Global.DirectDnsTag}-{directDnsTagIndex++}"; + dnsServer.skipFallback = false; + dnsItem.servers.Add(JsonUtils.SerializeToNode(dnsServer, + new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull })); + } + } return; static List ParseDnsAddresses(string? dnsInput, string defaultAddress) @@ -249,7 +283,7 @@ public partial class CoreConfigV2rayService return addresses.Count > 0 ? addresses : new List { defaultAddress }; } - static object? CreateDnsServer(string dnsAddress, List domains, List? expectedIPs = null) + static DnsServer4Ray CreateDnsServer(string dnsAddress, List domains, List? expectedIPs = null) { var (domain, scheme, port, path) = Utils.ParseUrl(dnsAddress); var domainFinal = dnsAddress; @@ -272,13 +306,10 @@ public partial class CoreConfigV2rayService domains = domains.Count > 0 ? domains : null, expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null }; - return JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }); + return dnsServer; } - void AddDnsServers(List dnsAddresses, List domains, List? expectedIPs = null) + void AddDnsServers(List dnsAddresses, List domains, bool isDirectDns = false, List? expectedIPs = null) { if (domains.Count <= 0) { @@ -286,7 +317,14 @@ public partial class CoreConfigV2rayService } foreach (var dnsAddress in dnsAddresses) { - dnsItem.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs)); + var dnsServer = CreateDnsServer(dnsAddress, domains, expectedIPs); + if (isDirectDns) + { + dnsServer.tag = $"{Global.DirectDnsTag}-{directDnsTagIndex++}"; + } + var dnsServerNode = JsonUtils.SerializeToNode(dnsServer, + new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); + dnsItem.servers.Add(dnsServerNode); } } } From 9f6237fb21536ca8203895ede8e7c6f6954bf0f1 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sun, 1 Mar 2026 08:11:40 +0000 Subject: [PATCH 05/15] Speedtest respect user-set core type (#8856) * Respect user-set core type * Allow test group --- .../ServiceLib/Handler/CoreConfigHandler.cs | 6 ++--- v2rayN/ServiceLib/Manager/AppManager.cs | 6 +++++ v2rayN/ServiceLib/Manager/CoreManager.cs | 2 +- .../ServiceLib/Manager/GroupProfileManager.cs | 11 +-------- v2rayN/ServiceLib/Models/ServerTestItem.cs | 2 ++ .../Singbox/CoreConfigSingboxService.cs | 4 ++-- .../V2ray/CoreConfigV2rayService.cs | 8 +++---- .../ServiceLib/Services/SpeedtestService.cs | 24 +++++++++++++------ 8 files changed, 35 insertions(+), 28 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index 1806ad26..474234e4 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -99,11 +99,9 @@ public static class CoreConfigHandler }; var builderResult = await CoreConfigContextBuilder.Build(config, dummyNode); var context = builderResult.Context; - var ids = selecteds.Where(serverTestItem => !serverTestItem.IndexId.IsNullOrEmpty()) - .Select(serverTestItem => serverTestItem.IndexId); - var nodes = await AppManager.Instance.GetProfileItemsByIndexIds(ids); - foreach (var node in nodes) + foreach (var testItem in selecteds) { + var node = testItem.Profile; var (actNode, _) = await CoreConfigContextBuilder.ResolveNodeAsync(context, node, true); if (node.IndexId == actNode.IndexId) { diff --git a/v2rayN/ServiceLib/Manager/AppManager.cs b/v2rayN/ServiceLib/Manager/AppManager.cs index e773c2ef..87b035e9 100644 --- a/v2rayN/ServiceLib/Manager/AppManager.cs +++ b/v2rayN/ServiceLib/Manager/AppManager.cs @@ -242,6 +242,12 @@ public sealed class AppManager .ToListAsync(); } + public async Task> GetProfileItemsByIndexIdsAsMap(IEnumerable indexIds) + { + var items = await GetProfileItemsByIndexIds(indexIds); + return items.ToDictionary(it => it.IndexId); + } + public async Task GetProfileItemViaRemarks(string? remarks) { if (remarks.IsNullOrEmpty()) diff --git a/v2rayN/ServiceLib/Manager/CoreManager.cs b/v2rayN/ServiceLib/Manager/CoreManager.cs index b2df4562..a55be232 100644 --- a/v2rayN/ServiceLib/Manager/CoreManager.cs +++ b/v2rayN/ServiceLib/Manager/CoreManager.cs @@ -98,7 +98,7 @@ public class CoreManager public async Task LoadCoreConfigSpeedtest(List selecteds) { - var coreType = selecteds.Any(t => Global.SingboxOnlyConfigType.Contains(t.ConfigType)) ? ECoreType.sing_box : ECoreType.Xray; + var coreType = selecteds.FirstOrDefault()?.CoreType == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray; var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); var configPath = Utils.GetBinConfigPath(fileName); var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType); diff --git a/v2rayN/ServiceLib/Manager/GroupProfileManager.cs b/v2rayN/ServiceLib/Manager/GroupProfileManager.cs index 510667dd..9e0cceb0 100644 --- a/v2rayN/ServiceLib/Manager/GroupProfileManager.cs +++ b/v2rayN/ServiceLib/Manager/GroupProfileManager.cs @@ -103,16 +103,7 @@ public class GroupProfileManager return []; } - var childProfiles = await AppManager.Instance.GetProfileItemsByIndexIds(childProfileIds); - if (childProfiles == null || childProfiles.Count == 0) - { - return []; - } - - var profileMap = childProfiles - .Where(p => p != null && !p.IndexId.IsNullOrEmpty()) - .GroupBy(p => p!.IndexId!) - .ToDictionary(g => g.Key, g => g.First()); + var profileMap = await AppManager.Instance.GetProfileItemsByIndexIdsAsMap(childProfileIds); var ordered = new List(childProfileIds.Count); foreach (var id in childProfileIds) diff --git a/v2rayN/ServiceLib/Models/ServerTestItem.cs b/v2rayN/ServiceLib/Models/ServerTestItem.cs index c16a598f..beef2438 100644 --- a/v2rayN/ServiceLib/Models/ServerTestItem.cs +++ b/v2rayN/ServiceLib/Models/ServerTestItem.cs @@ -9,4 +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 ECoreType CoreType { get; set; } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs index 87b59e11..b13fc7f1 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs @@ -149,11 +149,11 @@ public partial class CoreConfigSingboxService(CoreConfigContext context) foreach (var it in selecteds) { - if (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) + if (!(Global.SingboxSupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType())) { continue; } - if (it.Port <= 0) + if (!it.ConfigType.IsComplexType() && it.Port <= 0) { continue; } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs index e34dceb6..e5182c97 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs @@ -112,11 +112,11 @@ public partial class CoreConfigV2rayService(CoreConfigContext context) foreach (var it in selecteds) { - if (!Global.XraySupportConfigType.Contains(it.ConfigType)) + if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType())) { continue; } - if (it.Port <= 0) + if (!it.ConfigType.IsComplexType() && it.Port <= 0) { continue; } @@ -180,13 +180,13 @@ public partial class CoreConfigV2rayService(CoreConfigContext context) //rule RulesItem4Ray rule = new() { - inboundTag = new List { inbound.tag }, + inboundTag = [inbound.tag], outboundTag = tag, type = "field" }; if (isBalancer) { - rule.balancerTag = tag; + rule.balancerTag = tag + Global.BalancerTagSuffix; rule.outboundTag = null; } _coreConfig.routing.rules.Add(rule); diff --git a/v2rayN/ServiceLib/Services/SpeedtestService.cs b/v2rayN/ServiceLib/Services/SpeedtestService.cs index 207f7dfc..362f64d0 100644 --- a/v2rayN/ServiceLib/Services/SpeedtestService.cs +++ b/v2rayN/ServiceLib/Services/SpeedtestService.cs @@ -61,26 +61,36 @@ public class SpeedtestService(Config config, Func updateF private async Task> GetClearItem(ESpeedActionType actionType, List selecteds) { - var lstSelected = new List(); - foreach (var it in selecteds) + var lstSelected = new List(selecteds.Count); + var ids = selecteds.Where(it => !it.IndexId.IsNullOrEmpty() + && it.ConfigType != EConfigType.Custom + && (it.ConfigType.IsComplexType() || it.Port > 0)) + .Select(it => it.IndexId) + .ToList(); + var profileMap = await AppManager.Instance.GetProfileItemsByIndexIdsAsMap(ids); + for (var i = 0; i < selecteds.Count; i++) { - if (it.ConfigType.IsComplexType()) + var it = selecteds[i]; + if (it.ConfigType == EConfigType.Custom) { continue; } - if (it.Port <= 0) + if (!it.ConfigType.IsComplexType() && it.Port <= 0) { continue; } + var profile = profileMap.GetValueOrDefault(it.IndexId, it); lstSelected.Add(new ServerTestItem() { IndexId = it.IndexId, Address = it.Address, Port = it.Port, ConfigType = it.ConfigType, - QueueNum = selecteds.IndexOf(it) + QueueNum = i, + Profile = profile, + CoreType = AppManager.Instance.GetCoreType(profile, it.ConfigType), }); } @@ -353,8 +363,8 @@ public class SpeedtestService(Config config, Func updateF private List> GetTestBatchItem(List lstSelected, int pageSize) { List> lstTest = new(); - var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList(); - var lst2 = lstSelected.Where(t => Global.SingboxOnlyConfigType.Contains(t.ConfigType)).ToList(); + var lst1 = lstSelected.Where(t => t.CoreType == ECoreType.Xray).ToList(); + var lst2 = lstSelected.Where(t => t.CoreType == ECoreType.sing_box).ToList(); for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++) { From a71ebbd01cf4e4c9a10c6d36d593cd24f788e24f Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sun, 1 Mar 2026 09:41:59 +0000 Subject: [PATCH 06/15] Optimize (#8862) * Relax group type restrictions * Optimize db read --- v2rayN/ServiceLib/Manager/AppManager.cs | 18 ++++++++++++++++++ .../ServiceLib/Manager/GroupProfileManager.cs | 18 ++++-------------- .../ViewModels/AddGroupServerViewModel.cs | 11 ++--------- .../ViewModels/ProfilesSelectViewModel.cs | 14 +------------- .../ServiceLib/ViewModels/ProfilesViewModel.cs | 9 +-------- .../Views/AddGroupServerWindow.axaml.cs | 9 +-------- .../Views/SubEditWindow.axaml.cs | 4 ++-- .../v2rayN/Views/AddGroupServerWindow.xaml.cs | 9 +-------- v2rayN/v2rayN/Views/SubEditWindow.xaml.cs | 4 ++-- 9 files changed, 32 insertions(+), 64 deletions(-) diff --git a/v2rayN/ServiceLib/Manager/AppManager.cs b/v2rayN/ServiceLib/Manager/AppManager.cs index 87b035e9..43c5611d 100644 --- a/v2rayN/ServiceLib/Manager/AppManager.cs +++ b/v2rayN/ServiceLib/Manager/AppManager.cs @@ -248,6 +248,24 @@ public sealed class AppManager return items.ToDictionary(it => it.IndexId); } + public async Task> GetProfileItemsOrderedByIndexIds(IEnumerable indexIds) + { + var idList = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList(); + if (idList.Count == 0) + { + return []; + } + + var items = await SQLiteHelper.Instance.TableAsync() + .Where(it => idList.Contains(it.IndexId)) + .ToListAsync(); + var itemMap = items.ToDictionary(it => it.IndexId); + + return idList.Select(id => itemMap.GetValueOrDefault(id)) + .Where(item => item != null) + .ToList(); + } + public async Task GetProfileItemViaRemarks(string? remarks) { if (remarks.IsNullOrEmpty()) diff --git a/v2rayN/ServiceLib/Manager/GroupProfileManager.cs b/v2rayN/ServiceLib/Manager/GroupProfileManager.cs index 9e0cceb0..8a7389a0 100644 --- a/v2rayN/ServiceLib/Manager/GroupProfileManager.cs +++ b/v2rayN/ServiceLib/Manager/GroupProfileManager.cs @@ -52,10 +52,10 @@ public class GroupProfileManager return false; } - foreach (var child in childIds) + var childItems = await AppManager.Instance.GetProfileItemsByIndexIds(childIds); + foreach (var childItem in childItems) { - var childItem = await AppManager.Instance.GetProfileItem(child); - if (await HasCycle(child, childItem?.GetProtocolExtra(), visited, stack)) + if (await HasCycle(childItem.IndexId, childItem?.GetProtocolExtra(), visited, stack)) { return true; } @@ -103,17 +103,7 @@ public class GroupProfileManager return []; } - var profileMap = await AppManager.Instance.GetProfileItemsByIndexIdsAsMap(childProfileIds); - - var ordered = new List(childProfileIds.Count); - foreach (var id in childProfileIds) - { - if (id != null && profileMap.TryGetValue(id, out var item) && item != null) - { - ordered.Add(item); - } - } - + var ordered = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(childProfileIds); return ordered; } diff --git a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs index d30917ec..7d4d0948 100644 --- a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs @@ -99,15 +99,8 @@ public class AddGroupServerViewModel : MyReactiveObject Filter = protocolExtra?.Filter; var childIndexIds = Utils.String2List(protocolExtra?.ChildItems) ?? []; - foreach (var item in childIndexIds) - { - var child = await AppManager.Instance.GetProfileItem(item); - if (child == null) - { - continue; - } - ChildItemsObs.Add(child); - } + var childItemList = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(childIndexIds); + ChildItemsObs.AddRange(childItemList); } public async Task ChildRemoveAsync() diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs index ce349cb4..eb010c58 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs @@ -255,19 +255,7 @@ public class ProfilesSelectViewModel : MyReactiveObject { return null; } - var lst = new List(); - foreach (var sp in SelectedProfiles) - { - if (string.IsNullOrEmpty(sp?.IndexId)) - { - continue; - } - var item = await AppManager.Instance.GetProfileItem(sp.IndexId); - if (item != null) - { - lst.Add(item); - } - } + var lst = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(SelectedProfiles.Select(sp => sp?.IndexId)); if (lst.Count == 0) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer); diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 2b0de98d..edeec49e 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -456,14 +456,7 @@ public class ProfilesViewModel : MyReactiveObject var orderProfiles = SelectedProfiles?.OrderBy(t => t.Sort); if (latest) { - foreach (var profile in orderProfiles) - { - var item = await AppManager.Instance.GetProfileItem(profile.IndexId); - if (item is not null) - { - lstSelected.Add(item); - } - } + lstSelected.AddRange(await AppManager.Instance.GetProfileItemsOrderedByIndexIds(orderProfiles.Select(sp => sp?.IndexId))); } else { diff --git a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs index 06784ac0..1a2af744 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs @@ -148,14 +148,7 @@ public partial class AddGroupServerWindow : WindowBase private async void MenuAddChild_Click(object? sender, RoutedEventArgs e) { var selectWindow = new ProfilesSelectWindow(); - if (ViewModel?.SelectedSource?.ConfigType == EConfigType.PolicyGroup) - { - selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true); - } - else - { - selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true); - } + selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true); selectWindow.AllowMultiSelect(true); var result = await selectWindow.ShowDialog(this); if (result == true) diff --git a/v2rayN/v2rayN.Desktop/Views/SubEditWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/SubEditWindow.axaml.cs index 9d749ba8..2d7358d0 100644 --- a/v2rayN/v2rayN.Desktop/Views/SubEditWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/SubEditWindow.axaml.cs @@ -59,7 +59,7 @@ public partial class SubEditWindow : WindowBase private async void BtnSelectPrevProfile_Click(object? sender, RoutedEventArgs e) { var selectWindow = new ProfilesSelectWindow(); - selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true); + selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true); var result = await selectWindow.ShowDialog(this); if (result == true) { @@ -74,7 +74,7 @@ public partial class SubEditWindow : WindowBase private async void BtnSelectNextProfile_Click(object? sender, RoutedEventArgs e) { var selectWindow = new ProfilesSelectWindow(); - selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true); + selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true); var result = await selectWindow.ShowDialog(this); if (result == true) { diff --git a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs index 965b19d9..134450b7 100644 --- a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs @@ -128,14 +128,7 @@ public partial class AddGroupServerWindow private async void MenuAddChild_Click(object sender, RoutedEventArgs e) { var selectWindow = new ProfilesSelectWindow(); - if (ViewModel?.SelectedSource?.ConfigType == EConfigType.PolicyGroup) - { - selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true); - } - else - { - selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true); - } + selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true); selectWindow.AllowMultiSelect(true); if (selectWindow.ShowDialog() == true) { diff --git a/v2rayN/v2rayN/Views/SubEditWindow.xaml.cs b/v2rayN/v2rayN/Views/SubEditWindow.xaml.cs index d1451a9c..6323034a 100644 --- a/v2rayN/v2rayN/Views/SubEditWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/SubEditWindow.xaml.cs @@ -53,7 +53,7 @@ public partial class SubEditWindow private async void BtnSelectPrevProfile_Click(object sender, RoutedEventArgs e) { var selectWindow = new ProfilesSelectWindow(); - selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true); + selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true); if (selectWindow.ShowDialog() == true) { var profile = await selectWindow.ProfileItem; @@ -67,7 +67,7 @@ public partial class SubEditWindow private async void BtnSelectNextProfile_Click(object sender, RoutedEventArgs e) { var selectWindow = new ProfilesSelectWindow(); - selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true); + selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true); if (selectWindow.ShowDialog() == true) { var profile = await selectWindow.ProfileItem; From 56f1794e47c99d1124e959cb5116dd528fc861f4 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sun, 1 Mar 2026 10:38:16 +0000 Subject: [PATCH 07/15] Fix DNS rule (#8866) --- .../CoreConfig/Singbox/SingboxDnsService.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs index 03f4e80c..4b8b65aa 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs @@ -148,15 +148,20 @@ public partial class CoreConfigSingboxService _coreConfig.dns ??= new Dns4Sbox(); _coreConfig.dns.rules ??= []; - _coreConfig.dns.rules.AddRange(new[] + _coreConfig.dns.rules.Add(new() { ip_accept_any = true, server = Global.SingboxHostsDNSTag }); + + if (context.ProtectDomainList.Count > 0) { - new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag }, - new Rule4Sbox + _coreConfig.dns.rules.Add(new() { server = Global.SingboxDirectDNSTag, strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom), domain = context.ProtectDomainList.ToList(), - }, + }); + } + + _coreConfig.dns.rules.AddRange(new[] + { new Rule4Sbox { server = Global.SingboxRemoteDNSTag, From c9df9a0001bb593a664d3883fbf347705e64d9a3 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Sun, 1 Mar 2026 11:44:44 +0000 Subject: [PATCH 08/15] Fix (#8868) --- .../Services/CoreConfig/Singbox/SingboxDnsService.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs index 4b8b65aa..7a5074bb 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs @@ -433,7 +433,11 @@ public partial class CoreConfigSingboxService localDnsServer.tag = tag; dns4Sbox.servers.Add(localDnsServer); - dns4Sbox.rules.Insert(0, BuildProtectDomainRule()); + var protectDomainRule = BuildProtectDomainRule(); + if (protectDomainRule != null) + { + dns4Sbox.rules.Insert(0, protectDomainRule); + } _coreConfig.dns = dns4Sbox; } @@ -455,8 +459,12 @@ public partial class CoreConfigSingboxService _coreConfig.dns?.servers?.Add(localDnsServer); } - private Rule4Sbox BuildProtectDomainRule() + private Rule4Sbox? BuildProtectDomainRule() { + if (context.ProtectDomainList.Count == 0) + { + return null; + } return new() { server = Global.SingboxLocalDNSTag, From cea725ae3d16a58dc03ef5c5ab3bb73593f5231d Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:31:34 +0800 Subject: [PATCH 09/15] 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 14b16776..0921f480 100644 --- a/v2rayN/Directory.Packages.props +++ b/v2rayN/Directory.Packages.props @@ -22,7 +22,7 @@ - + From bfa9eaa5eccc0565e73a698e0ea21098c20d32f6 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:31:57 +0800 Subject: [PATCH 10/15] up 7.19.1 --- 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 9c4b3432..e7dffdd2 100644 --- a/v2rayN/Directory.Build.props +++ b/v2rayN/Directory.Build.props @@ -1,7 +1,7 @@ - 7.19.0 + 7.19.1 From 65cf782eb0e3ea6f66adcc94eb26373e63b41a17 Mon Sep 17 00:00:00 2001 From: tt2563 <243264479+tt2563@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:44:53 +0800 Subject: [PATCH 11/15] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B9=81=E9=AB=94?= =?UTF-8?q?=E4=B8=AD=E6=96=87=E7=BF=BB=E8=AD=AF=20(#8873)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ertet --- v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 78 +++++++++++------------ 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 330a36e2..c4125679 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -127,7 +127,7 @@ 設定格式不正確 - 注意,自訂設定完全依賴您自己的設定,不能使用所有設定功能。如需使用系統代理請手動修改偵聽埠。 + 注意,自訂設定完全依賴您自行輸入的內容,部分功能可能無法使用。如需啟用系統代理,請手動調整監聽埠。 下載開始... @@ -139,7 +139,7 @@ 生成預設設定檔失敗 - 獲取預設設定失敗 + 取得預設設定失敗 匯入自訂設定失敗 @@ -148,7 +148,7 @@ 讀取設定失敗 - 請填寫正確格式的埠 + 請填寫有效的埠號 請填寫本機偵聽埠 @@ -247,7 +247,7 @@ 非 VMess 或 SS 協定 - 在資料夾 ({0}) 下未找到 Core 檔案 (檔案名: {1}),請下載後放入資料夾,下載網址: {2} + 在資料夾 ({0}) 中找不到 Core 檔案(檔名:{1})。請下載後放入該資料夾。下載網址:{2} 掃描完成,未發現有效二維碼 @@ -304,7 +304,7 @@ 是否確定移除規則? - {0},必填其中一項. + {0},至少需填寫其中一項。 別名 @@ -385,7 +385,7 @@ 所有 - 請瀏覽匯入設定 + 請選擇要匯入的設定檔 測試中... @@ -472,7 +472,7 @@ 語言 (需重啟) - 從剪貼簿導入分享連結 + 從剪貼簿匯入分享連結 掃描螢幕上的二維碼 @@ -616,10 +616,10 @@ SNI - 傳輸層安全 (TLS) + 傳輸層安全性 (TLS) - *預設 TCP,選錯會無法連接 + *預設 TCP,選錯會無法連線 Core 類型 @@ -652,7 +652,7 @@ SOCKS 埠 - *自訂設定的 Socks 埠值,可不設定;當設定此值後,將使用 Xray/sing-box (Tun) 額外啟動一個前置 Socks 服務,提供分流和速度顯示等功能 + *自訂設定的 Socks 埠值,可留空;當設定此值後,將使用 Xray/sing-box (Tun) 額外啟動一個前置 Socks 服務,提供分流和速度顯示等功能 瀏覽 @@ -1309,7 +1309,7 @@ 安裝字體到系統中,選擇或填入字體名稱,重新啟動後生效 - 是否確定退出? + 確定要退出嗎? 備註備忘 @@ -1495,13 +1495,13 @@ 策略組類型 - 添加策略組 + 新增策略組 - 添加鏈式代理 + 新增鏈式代理 - 添加子配置 + 新增子配置 刪除子配置 @@ -1591,81 +1591,81 @@ 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 模組。 - Port hopping interval + 連接埠跳轉間隔 - 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 fd7cf0d453c37c7ece1929cb777ac1bddef97147 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Mon, 2 Mar 2026 11:46:01 +0000 Subject: [PATCH 12/15] Fix xray custom dns (#8872) --- v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs index 021fee2f..6b0d335d 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs @@ -437,7 +437,7 @@ public partial class CoreConfigV2rayService FillDnsDomainsCustom(obj); - _coreConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(obj)); + _coreConfig.dns = obj; } catch (Exception ex) { From 9a3604e89b2fc4862427a42b513f23d9df9d2812 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Mon, 2 Mar 2026 20:00:02 +0800 Subject: [PATCH 13/15] Bug fix https://github.com/2dust/v2rayN/issues/8874 --- 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 f3ef7bce..0153f6a1 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -231,6 +231,7 @@ public static class ConfigHandler item.Address = profileItem.Address; item.Port = profileItem.Port; + item.Username = profileItem.Username; item.Password = profileItem.Password; item.Network = profileItem.Network; From f24a79aa2c40ceb9adbffa2691fb182b0b3f4f0e Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Mon, 2 Mar 2026 20:18:07 +0800 Subject: [PATCH 14/15] Bug fix https://github.com/2dust/v2rayN/issues/8875 --- v2rayN/v2rayN/Views/DNSSettingWindow.xaml.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/v2rayN/v2rayN/Views/DNSSettingWindow.xaml.cs b/v2rayN/v2rayN/Views/DNSSettingWindow.xaml.cs index 72b09149..90999417 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.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.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.Text).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.Strategy4Freedom, v => v.cmbDirectDNSStrategy.SelectedItem).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.SelectedItem).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 4d2f32099e66a0103eaa82d1dabe0ef018645234 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:07:48 +0800 Subject: [PATCH 15/15] Bug fix https://github.com/2dust/v2rayN/issues/8879 --- .../V2ray/CoreConfigV2rayService.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs index e5182c97..ca8eeb0d 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs @@ -301,6 +301,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context) GenLog(); _coreConfig.outbounds.Clear(); GenOutbounds(); + GenStatistic(); var protectNode = new ProfileItem() { @@ -326,18 +327,17 @@ public partial class CoreConfigV2rayService(CoreConfigContext context) Node = protectNode, }).BuildProxyOutbound("tun-project-ss")); + _coreConfig.routing.rules ??= []; var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 }; - _coreConfig.routing.rules = - [ - new() - { - inboundTag = new List { "proxy-relay-ss" }, - outboundTag = hasBalancer ? null : Global.ProxyTag, - balancerTag = hasBalancer ? Global.ProxyTag + Global.BalancerTagSuffix: null, - type = "field" - } - ]; - _coreConfig.inbounds.Clear(); + _coreConfig.routing.rules.Add(new() + { + inboundTag = ["proxy-relay-ss"], + outboundTag = hasBalancer ? null : Global.ProxyTag, + balancerTag = hasBalancer ? Global.ProxyTag + Global.BalancerTagSuffix : null, + type = "field" + }); + + //_coreConfig.inbounds.Clear(); var configNode = JsonUtils.ParseJson(JsonUtils.Serialize(_coreConfig))!; configNode["inbounds"]!.AsArray().Add(new