From 0a2b4374f9732cf2f782bc0c4757f4a24c719f53 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 24 Jul 2025 12:51:17 +0800 Subject: [PATCH] Fixes --- v2rayN/ServiceLib/Global.cs | 4 +- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 4 +- v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 27 + v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 9 + v2rayN/ServiceLib/Resx/ResUI.hu.resx | 9 + v2rayN/ServiceLib/Resx/ResUI.resx | 9 + v2rayN/ServiceLib/Resx/ResUI.ru.resx | 9 + v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 9 + v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 9 + .../CoreConfig/CoreConfigSingboxService.cs | 3 +- .../CoreConfig/CoreConfigV2rayService.cs | 6 +- .../Minimal/CoreConfigBrookService.cs | 1 + .../Minimal/CoreConfigHy2Service.cs | 3 +- .../Minimal/CoreConfigJuicityService.cs | 5 +- .../Minimal/CoreConfigNaiveService.cs | 3 +- .../Minimal/CoreConfigShadowquicService.cs | 17 +- .../Minimal/CoreConfigTuicService.cs | 17 +- .../Views/AddServerWindow.axaml.cs | 6 +- .../Views/OptionSettingWindow.axaml | 375 ++++++++--- .../Views/OptionSettingWindow.axaml.cs | 23 + v2rayN/v2rayN/Views/AddServerWindow.xaml.cs | 6 +- v2rayN/v2rayN/Views/OptionSettingWindow.xaml | 593 +++++++++--------- 22 files changed, 748 insertions(+), 399 deletions(-) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index b12e1f50..c2001036 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -281,13 +281,13 @@ public class Global public static readonly List Hysteria2CoreTypes = [ "sing_box", - "Hysteria2" + "hysteria2" ]; public static readonly List TuicCoreTypes = [ "sing_box", - "TUIC" + "tuic" ]; public static readonly List SupportSplitConfigTypes = diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 4e289bd4..848d4f35 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -698,7 +698,7 @@ public class ConfigHandler public static async Task AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.Hysteria2; - profileItem.CoreType = ECoreType.sing_box; + //profileItem.CoreType = ECoreType.sing_box; profileItem.Address = profileItem.Address.TrimEx(); profileItem.Id = profileItem.Id.TrimEx(); @@ -731,7 +731,7 @@ public class ConfigHandler public static async Task AddTuicServer(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.TUIC; - profileItem.CoreType = ECoreType.sing_box; + //profileItem.CoreType = ECoreType.sing_box; profileItem.Address = profileItem.Address.TrimEx(); profileItem.Id = profileItem.Id.TrimEx(); diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index ca554860..62ed61c2 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -3426,6 +3426,33 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. 的本地化字符串。 + /// + public static string TbSettingsSplitCoreDoc1 { + get { + return ResourceManager.GetString("TbSettingsSplitCoreDoc1", resourceCulture); + } + } + + /// + /// 查找类似 Routing Core defaults to sing-box when Tun is enabled. 的本地化字符串。 + /// + public static string TbSettingsSplitCoreDoc2 { + get { + return ResourceManager.GetString("TbSettingsSplitCoreDoc2", resourceCulture); + } + } + + /// + /// 查找类似 Enable separation of outbound and routing cores 的本地化字符串。 + /// + public static string TbSettingsSplitCoreEnable { + get { + return ResourceManager.GetString("TbSettingsSplitCoreEnable", resourceCulture); + } + } + /// /// 查找类似 sing-box ruleset files source (optional) 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index bd2cb887..ee776c78 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1404,4 +1404,13 @@ Add [Anytls] Configuration + + Enable separation of outbound and routing cores + + + Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. + + + Routing Core defaults to sing-box when Tun is enabled. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 0fc30153..6fdfc7dd 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1404,4 +1404,13 @@ [Anytls] konfiguráció hozzáadása + + Enable separation of outbound and routing cores + + + Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. + + + Routing Core defaults to sing-box when Tun is enabled. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 7ecfcc98..bffcea43 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1404,4 +1404,13 @@ Add [Anytls] Configuration + + Enable separation of outbound and routing cores + + + Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. + + + Routing Core defaults to sing-box when Tun is enabled. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 25f17e0d..42843dc4 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1404,4 +1404,13 @@ Добавить сервер [Anytls] + + Enable separation of outbound and routing cores + + + Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. + + + Routing Core defaults to sing-box when Tun is enabled. + \ 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 95d69e9b..a9ed87cc 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1401,4 +1401,13 @@ 添加 [Anytls] 配置文件 + + 启用出站核心与路由核心分离 + + + 出站与路由分离,出站与分流 Core 类型不同时,将启用两个核心 + + + 启用 Tun 时,路由 Core 为 sing-box + \ 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 0b23885c..3e99f83a 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1401,4 +1401,13 @@ 新增 [Anytls] 設定檔 + + Enable separation of outbound and routing cores + + + Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. + + + Routing Core defaults to sing-box when Tun is enabled. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 14a531d4..ea5d787d 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -2,6 +2,7 @@ using System.Data; using System.Net; using System.Net.NetworkInformation; using System.Reactive; +using System.Text.Json.Nodes; using DynamicData; using ServiceLib.Models; @@ -587,7 +588,7 @@ public class CoreConfigSingboxService config.Remove("route"); - ret.Data = config.ToJsonString(new() { WriteIndented = true }); + ret.Data = JsonUtils.Serialize(config, true); return ret; } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index c15c05ff..966e23f5 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -453,6 +453,10 @@ public class CoreConfigV2rayService await GenLog(v2rayConfig); var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.split, true); + inbound.sniffing = new Sniffing4Ray + { + enabled = false + }; v2rayConfig.inbounds = new() { inbound }; @@ -469,7 +473,7 @@ public class CoreConfigV2rayService config.Remove("routing"); - ret.Data = config.ToJsonString(new() { WriteIndented = true }); + ret.Data = JsonUtils.Serialize(config, true); return ret; } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs index b3b4cef7..6ef1353e 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs @@ -36,6 +36,7 @@ public class CoreConfigBrookService processArgs += " --server " + node.Address + ":" + node.Port; processArgs += " --password " + node.Id; + ret.Success = true; ret.Data = processArgs; return await Task.FromResult(ret); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs index 25026dba..3241aecd 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs @@ -74,7 +74,8 @@ public class CoreConfigHy2Service configJsonNode["bandwidth"] = bandwidthObject; } - ret.Data = configJsonNode.ToJsonString(new() { WriteIndented = true }); + ret.Success = true; + ret.Data = JsonUtils.Serialize(configJsonNode, true); return await Task.FromResult(ret); } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs index a08b022b..66fccbbd 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs @@ -56,9 +56,10 @@ public class CoreConfigJuicityService configJsonNode["sni"] = node.Sni; } configJsonNode["allow_insecure"] = node.AllowInsecure == "true"; - configJsonNode["congestion_control"] = "bbr"; + configJsonNode["congestion_control"] = node.HeaderType; - ret.Data = configJsonNode.ToJsonString(new() { WriteIndented = true }); + ret.Success = true; + ret.Data = JsonUtils.Serialize(configJsonNode, true); return await Task.FromResult(ret); } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs index 8307d5b0..544e6f92 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs @@ -39,7 +39,8 @@ public class CoreConfigNaiveService // outbound configJsonNode["proxy"] = (node.Network == "quic" ? "quic://" : Global.HttpsProtocol) + node.Id + "@" + node.Address + ":" + node.Port; - ret.Data = configJsonNode.ToJsonString(new() { WriteIndented = true }); + ret.Success = true; + ret.Data = JsonUtils.Serialize(configJsonNode, true); return await Task.FromResult(ret); } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs index 1d224210..232b56df 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Nodes; + namespace ServiceLib.Services.CoreConfig.Minimal; public class CoreConfigShadowquicService { @@ -51,14 +53,24 @@ public class CoreConfigShadowquicService configYamlNode["inbound"] = inboundNode; // outbound + var alpn = new JsonArray(); + foreach (var item in node.GetAlpn() ?? new List()) + { + alpn.Add(item); + } + if (alpn.Count == 0) + { + alpn.Add("h3"); + } + var outboundNode = new Dictionary { ["type"] = "shadowquic", ["addr"] = node.Address + ":" + node.Port, ["password"] = node.Id, ["username"] = node.Security, - ["alpn"] = new List { "h3" }, - ["congestion-control"] = "bbr", + ["alpn"] = alpn, + ["congestion-control"] = node.HeaderType, ["zero-rtt"] = true }; if (node.Sni.IsNotEmpty()) @@ -67,6 +79,7 @@ public class CoreConfigShadowquicService } configYamlNode["outbound"] = outboundNode; + ret.Success = true; ret.Data = YamlUtils.ToYaml(configYamlNode); return await Task.FromResult(ret); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs index 3f95bebd..55dfecb1 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs @@ -51,17 +51,28 @@ public class CoreConfigTuicService }; // outbound + var alpn = new JsonArray(); + foreach(var item in node.GetAlpn() ?? new List()) + { + alpn.Add(item); + } + if (alpn.Count == 0) + { + alpn.Add("h3"); + } + configJsonNode["relay"] = new JsonObject { ["server"] = node.Address + ":" + node.Port, ["uuid"] = node.Id, ["password"] = node.Security, ["udp_relay_mode"] = "quic", - ["congestion_control"] = "bbr", - ["alpn"] = new JsonArray { "h3", "spdy/3.1" } + ["congestion_control"] = node.HeaderType, + ["alpn"] = alpn }; - ret.Data = configJsonNode.ToJsonString(new() { WriteIndented = true }); + ret.Success = true; + ret.Data = JsonUtils.Serialize(configJsonNode, true); return await Task.FromResult(ret); } diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs index 9d24175f..3a113722 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs @@ -78,7 +78,8 @@ public partial class AddServerWindow : WindowBase gridHysteria2.IsVisible = true; sepa2.IsVisible = false; gridTransport.IsVisible = false; - cmbCoreType.IsEnabled = false; + //cmbCoreType.IsEnabled = false; + cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes; cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; break; @@ -87,7 +88,8 @@ public partial class AddServerWindow : WindowBase gridTuic.IsVisible = true; sepa2.IsVisible = false; gridTransport.IsVisible = false; - cmbCoreType.IsEnabled = false; + //cmbCoreType.IsEnabled = false; + cmbCoreType.ItemsSource = Global.TuicCoreTypes; cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml index a75ed6fc..e61c16ea 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -802,101 +802,298 @@ - - - + + + + + + + + + - - + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs index 010488e5..152e629c 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs @@ -41,6 +41,17 @@ public partial class OptionSettingWindow : WindowBase cmbCoreType6.ItemsSource = Global.CoreTypes; cmbCoreType9.ItemsSource = Global.CoreTypes; + cmbCoreSplitRouteType.ItemsSource = Global.CoreTypes; + + cmbCoreSplitType1.ItemsSource = Global.CoreTypes; + cmbCoreSplitType3.ItemsSource = Global.CoreTypes; + cmbCoreSplitType4.ItemsSource = Global.CoreTypes; + cmbCoreSplitType5.ItemsSource = Global.CoreTypes; + cmbCoreSplitType6.ItemsSource = Global.CoreTypes; + cmbCoreSplitType7.ItemsSource = Global.Hysteria2CoreTypes; + cmbCoreSplitType8.ItemsSource = Global.TuicCoreTypes; + cmbCoreSplitType9.ItemsSource = Global.CoreTypes; + cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(2, 7).ToList(); cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).ToList(); cmbSpeedTestUrl.ItemsSource = Global.SpeedTestUrls; @@ -119,6 +130,18 @@ public partial class OptionSettingWindow : WindowBase this.Bind(ViewModel, vm => vm.CoreType6, v => v.cmbCoreType6.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.EnableSplitCore, v => v.togCoreSplit.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.RouteSplitCoreType, v => v.cmbCoreSplitRouteType.SelectedValue).DisposeWith(disposables); + + this.Bind(ViewModel, vm => vm.SplitCoreType1, v => v.cmbCoreSplitType1.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType3, v => v.cmbCoreSplitType3.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType4, v => v.cmbCoreSplitType4.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType5, v => v.cmbCoreSplitType5.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType6, v => v.cmbCoreSplitType6.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType7, v => v.cmbCoreSplitType7.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType8, v => v.cmbCoreSplitType8.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType9, v => v.cmbCoreSplitType9.SelectedValue).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); }); diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs b/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs index b06002a9..664a47bf 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs @@ -72,7 +72,8 @@ public partial class AddServerWindow gridHysteria2.Visibility = Visibility.Visible; sepa2.Visibility = Visibility.Collapsed; gridTransport.Visibility = Visibility.Collapsed; - cmbCoreType.IsEnabled = false; + //cmbCoreType.IsEnabled = false; + cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes; cmbFingerprint.IsEnabled = false; cmbFingerprint.Text = string.Empty; break; @@ -81,7 +82,8 @@ public partial class AddServerWindow gridTuic.Visibility = Visibility.Visible; sepa2.Visibility = Visibility.Collapsed; gridTransport.Visibility = Visibility.Collapsed; - cmbCoreType.IsEnabled = false; + //cmbCoreType.IsEnabled = false; + cmbCoreType.ItemsSource = Global.TuicCoreTypes; cmbFingerprint.IsEnabled = false; cmbFingerprint.Text = string.Empty; diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index 07d01f53..0710d65a 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -1107,318 +1107,329 @@ - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Margin="{StaticResource Margin8}"> + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + + + Margin="{StaticResource Margin8}"> + + + + + + + + + + + + + + + + + + + - - + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + - +