diff --git a/v2rayN/ServiceLib/Common/Utils.cs b/v2rayN/ServiceLib/Common/Utils.cs index 38869ebb..f7fd3445 100644 --- a/v2rayN/ServiceLib/Common/Utils.cs +++ b/v2rayN/ServiceLib/Common/Utils.cs @@ -813,7 +813,7 @@ public class Utils } } - public static string GetBinConfigPath(string filename = "") + public static string GetBinConfigPath(string filename = "", ECoreType coreType = ECoreType.v2rayN) { var tempPath = Path.Combine(StartupPath(), "binConfigs"); if (!Directory.Exists(tempPath)) @@ -827,10 +827,27 @@ public class Utils } else { - return Path.Combine(tempPath, filename); + return Path.Combine(tempPath, GetBinConfigFileName(filename, coreType)); } } + public static string GetBinConfigFileName(string filename, ECoreType coreType = ECoreType.v2rayN) + { + var fileSuffix = coreType switch + { + ECoreType.sing_box => ".json", + ECoreType.Xray => ".json", + ECoreType.hysteria2 => ".json", + ECoreType.naiveproxy => ".json", + ECoreType.tuic => ".json", + ECoreType.juicity => ".json", + ECoreType.brook => ".cac", + ECoreType.shadowquic => ".yaml", + _ => string.Empty + }; + return filename.EndsWith(fileSuffix) ? filename : $"{filename}{fileSuffix}"; + } + #endregion TempPath #region Platform diff --git a/v2rayN/ServiceLib/Enums/EConfigType.cs b/v2rayN/ServiceLib/Enums/EConfigType.cs index 6698f962..71878b27 100644 --- a/v2rayN/ServiceLib/Enums/EConfigType.cs +++ b/v2rayN/ServiceLib/Enums/EConfigType.cs @@ -12,5 +12,9 @@ public enum EConfigType TUIC = 8, WireGuard = 9, HTTP = 10, - Anytls = 11 + Anytls = 11, + NaiveProxy = 100, + Juicity = 101, + Brook = 102, + Shadowquic = 103, } diff --git a/v2rayN/ServiceLib/Enums/EInboundProtocol.cs b/v2rayN/ServiceLib/Enums/EInboundProtocol.cs index 768a428b..8c75c65f 100644 --- a/v2rayN/ServiceLib/Enums/EInboundProtocol.cs +++ b/v2rayN/ServiceLib/Enums/EInboundProtocol.cs @@ -9,5 +9,6 @@ public enum EInboundProtocol api, api2, mixed, + split, speedtest = 21 } diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 4888ab6a..773f0bc9 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -12,8 +12,8 @@ public class Global public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="; public const string ConfigFileName = "guiNConfig.json"; - public const string CoreConfigFileName = "config.json"; - public const string CorePreConfigFileName = "configPre.json"; + public const string CoreConfigFileName = "config"; + public const string CorePreConfigFileName = "configPre"; public const string CoreSpeedtestConfigFileName = "configTest{0}.json"; public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json"; public const string ClashMixinConfigFileName = "Mixin.yaml"; @@ -170,7 +170,11 @@ public class Global { EConfigType.Hysteria2, "hysteria2://" }, { EConfigType.TUIC, "tuic://" }, { EConfigType.WireGuard, "wireguard://" }, - { EConfigType.Anytls, "anytls://" } + { EConfigType.Anytls, "anytls://" }, + { EConfigType.NaiveProxy, "naive://" }, + { EConfigType.Juicity, "juicity://" }, + { EConfigType.Brook, "brook://" }, + { EConfigType.Shadowquic, "shadowquic://" } }; public static readonly Dictionary ProtocolTypes = new() @@ -184,7 +188,11 @@ public class Global { EConfigType.Hysteria2, "hysteria2" }, { EConfigType.TUIC, "tuic" }, { EConfigType.WireGuard, "wireguard" }, - { EConfigType.Anytls, "anytls" } + { EConfigType.Anytls, "anytls" }, + { EConfigType.NaiveProxy, "naiveproxy" }, + { EConfigType.Juicity, "juicity" }, + { EConfigType.Brook, "brook" }, + { EConfigType.Shadowquic, "shadowquic" } }; public static readonly List VmessSecurities = @@ -278,6 +286,75 @@ public class Global "sing_box" ]; + public static readonly List Hysteria2CoreTypes = + [ + "sing_box", + "hysteria2" + ]; + + public static readonly List TuicCoreTypes = + [ + "sing_box", + "tuic" + ]; + + public static readonly List NaiveProxyCoreTypes = + [ + "naiveproxy" + ]; + + public static readonly List JuicityProxyCoreTypes = + [ + "juicity" + ]; + + public static readonly List BrookCoreTypes = + [ + "brook" + ]; + + public static readonly List ShadowquicCoreTypes = + [ + "shadowquic" + ]; + + public static readonly List SupportSplitConfigTypes = + [ + EConfigType.VMess, + EConfigType.VLESS, + EConfigType.Shadowsocks, + EConfigType.Trojan, + EConfigType.Hysteria2, + EConfigType.TUIC, + EConfigType.WireGuard, + EConfigType.SOCKS, + ]; + + public static readonly HashSet XraySupportConfigType = + [ + EConfigType.VMess, + EConfigType.VLESS, + EConfigType.Shadowsocks, + EConfigType.Trojan, + EConfigType.WireGuard, + EConfigType.SOCKS, + EConfigType.HTTP, + ]; + + public static readonly HashSet SingboxSupportConfigType = + [ + EConfigType.VMess, + EConfigType.VLESS, + EConfigType.Shadowsocks, + EConfigType.Trojan, + EConfigType.Hysteria2, + EConfigType.TUIC, + EConfigType.Anytls, + EConfigType.WireGuard, + EConfigType.SOCKS, + EConfigType.HTTP, + ]; + public static readonly List DomainStrategies = [ "AsIs", @@ -464,13 +541,20 @@ public class Global "" ]; - public static readonly List TuicCongestionControls = + public static readonly List CongestionControls = [ "cubic", "new_reno", "bbr" ]; + public static readonly List NaiveProxyProtocols = + [ + "https", + "http", + "quic" + ]; + public static readonly List allowSelectType = [ "selector", diff --git a/v2rayN/ServiceLib/Handler/AppHandler.cs b/v2rayN/ServiceLib/Handler/AppHandler.cs index ad9a4029..f8495a79 100644 --- a/v2rayN/ServiceLib/Handler/AppHandler.cs +++ b/v2rayN/ServiceLib/Handler/AppHandler.cs @@ -1,3 +1,7 @@ +using DynamicData; +using ServiceLib.Enums; +using ServiceLib.Models; + namespace ServiceLib.Handler; public sealed class AppHandler @@ -231,9 +235,89 @@ public sealed class AppHandler return (ECoreType)profileItem.CoreType; } + return GetCoreType(eConfigType); + } + + public ECoreType GetCoreType(EConfigType eConfigType) + { var item = _config.CoreTypeItem?.FirstOrDefault(it => it.ConfigType == eConfigType); return item?.CoreType ?? ECoreType.Xray; } + public ECoreType GetSplitCoreType(ProfileItem profileItem, EConfigType eConfigType) + { + if (profileItem?.CoreType != null) + { + return (ECoreType)profileItem.CoreType; + } + + return GetSplitCoreType(eConfigType); + } + + public ECoreType GetSplitCoreType(EConfigType eConfigType) + { + var item = _config.SplitCoreItem.SplitCoreTypes?.FirstOrDefault(it => it.ConfigType == eConfigType); + return item?.CoreType ?? ECoreType.Xray; + } + + public (bool, ECoreType, ECoreType?) GetCoreAndPreType(ProfileItem profileItem) + { + var splitCore = _config.SplitCoreItem.EnableSplitCore; + var coreType = GetCoreType(profileItem, profileItem.ConfigType); + ECoreType? preCoreType = null; + + var pureEndpointCore = profileItem.CoreType ?? GetSplitCoreType(profileItem, profileItem.ConfigType); + var splitRouteCore = _config.SplitCoreItem.RouteCoreType; + var enableTun = _config.TunModeItem.EnableTun; + + if (profileItem.ConfigType == EConfigType.Custom) + { + splitCore = false; + coreType = profileItem.CoreType ?? ECoreType.Xray; + if (profileItem.PreSocksPort > 0) + { + preCoreType = enableTun ? ECoreType.sing_box : GetCoreType(profileItem.ConfigType); + } + else + { + preCoreType = null; + } + } + else if (!splitCore && profileItem.CoreType is not (ECoreType.Xray or ECoreType.sing_box)) + { + // Force SplitCore for cores that don't support direct routing (like Hysteria2, TUIC, etc.) + splitCore = true; + preCoreType = enableTun ? ECoreType.sing_box : splitRouteCore; + } + else if (splitCore) + { + // User explicitly enabled SplitCore + preCoreType = enableTun ? ECoreType.sing_box : splitRouteCore; + coreType = pureEndpointCore; + + if (preCoreType == coreType) + { + preCoreType = null; + splitCore = false; + } + } + else if (enableTun) // EnableTun is true but SplitCore is false + { + // TUN mode handling for Xray/sing_box cores + preCoreType = ECoreType.sing_box; + + if (preCoreType == coreType) // CoreType is sing_box + { + preCoreType = null; + } + else // CoreType is xray, etc. + { + // Force SplitCore for non-split cores + splitCore = true; + } + } + return (splitCore, coreType, preCoreType); + } + #endregion Core Type } diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 6c93d868..ab19578c 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -165,6 +165,13 @@ public class ConfigHandler config.SystemProxyItem.SystemProxyExceptions = Utils.IsWindows() ? Global.SystemProxyExceptionsWindows : Global.SystemProxyExceptionsLinux; } + config.SplitCoreItem ??= new() + { + EnableSplitCore = false, + SplitCoreTypes = new List(), + RouteCoreType = ECoreType.Xray + }; + return config; } @@ -263,6 +270,10 @@ public class ConfigHandler EConfigType.TUIC => await AddTuicServer(config, item), EConfigType.WireGuard => await AddWireguardServer(config, item), EConfigType.Anytls => await AddAnytlsServer(config, item), + EConfigType.NaiveProxy => await AddNaiveServer(config, item), + EConfigType.Juicity => await AddJuicityServer(config, item), + EConfigType.Brook => await AddBrookServer(config, item), + EConfigType.Shadowquic => await AddShadowquicServer(config, item), _ => -1, }; return ret; @@ -691,7 +702,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(); @@ -724,16 +735,16 @@ 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(); profileItem.Security = profileItem.Security.TrimEx(); profileItem.Network = string.Empty; - if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType)) + if (!Global.CongestionControls.Contains(profileItem.HeaderType)) { - profileItem.HeaderType = Global.TuicCongestionControls.FirstOrDefault()!; + profileItem.HeaderType = Global.CongestionControls.FirstOrDefault()!; } if (profileItem.StreamSecurity.IsNullOrEmpty()) @@ -816,6 +827,144 @@ public class ConfigHandler return 0; } + /// + /// Add or edit a Naive server + /// Validates and processes Naive-specific settings + /// + /// Current configuration + /// Naive profile to add + /// Whether to save to file + /// 0 if successful, -1 if failed + public static async Task AddNaiveServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.NaiveProxy; + profileItem.CoreType = ECoreType.naiveproxy; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Network = string.Empty; + if (!Global.NaiveProxyProtocols.Contains(profileItem.HeaderType)) + { + profileItem.HeaderType = Global.NaiveProxyProtocols.FirstOrDefault()!; + } + if (profileItem.StreamSecurity.IsNullOrEmpty()) + { + profileItem.StreamSecurity = Global.StreamSecurity; + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + await AddServerCommon(config, profileItem, toFile); + return 0; + } + + /// + /// Add or edit a Juicity server + /// Validates and processes Juicity-specific settings + /// + /// Current configuration + /// Juicity profile to add + /// Whether to save to file + /// 0 if successful, -1 if failed + public static async Task AddJuicityServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.Juicity; + profileItem.CoreType = ECoreType.juicity; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Network = string.Empty; + + if (!Global.CongestionControls.Contains(profileItem.HeaderType)) + { + profileItem.HeaderType = Global.CongestionControls.FirstOrDefault()!; + } + + if (profileItem.StreamSecurity.IsNullOrEmpty()) + { + profileItem.StreamSecurity = Global.StreamSecurity; + } + if (profileItem.Alpn.IsNullOrEmpty()) + { + profileItem.Alpn = "h3"; + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit a Brook server + /// Validates and processes Brook-specific settings + /// + /// Current configuration + /// Brook profile to add + /// Whether to save to file + /// 0 if successful, -1 if failed + public static async Task AddBrookServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.Brook; + profileItem.CoreType = ECoreType.brook; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Network = string.Empty; + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + await AddServerCommon(config, profileItem, toFile); + return 0; + } + + /// + /// Add or edit a Shadowquic server + /// Validates and processes Shadowquic-specific settings + /// + /// Current configuration + /// Shadowquic profile to add + /// Whether to save to file + /// 0 if successful, -1 if failed + public static async Task AddShadowquicServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.Shadowquic; + profileItem.CoreType = ECoreType.shadowquic; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Network = string.Empty; + + if (!Global.CongestionControls.Contains(profileItem.HeaderType)) + { + profileItem.HeaderType = Global.CongestionControls.FirstOrDefault()!; + } + + if (profileItem.StreamSecurity.IsNullOrEmpty()) + { + profileItem.StreamSecurity = Global.StreamSecurity; + } + if (profileItem.Alpn.IsNullOrEmpty()) + { + profileItem.Alpn = "h3"; + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + /// /// Sort the server list by the specified column /// Updates the sort order in the profile extension data @@ -1190,43 +1339,6 @@ public class ConfigHandler 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 - /// - /// Current configuration - /// Server node that might need pre-SOCKS - /// Core type being used - /// A SOCKS profile item or null if not needed - public static async Task GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType) - { - ProfileItem? itemSocks = null; - if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun) - { - itemSocks = new ProfileItem() - { - CoreType = ECoreType.sing_box, - ConfigType = EConfigType.SOCKS, - Address = Global.Loopback, - Sni = node.Address, //Tun2SocksAddress - Port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks) - }; - } - else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)) - { - var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray; - itemSocks = new ProfileItem() - { - CoreType = preCoreType, - ConfigType = EConfigType.SOCKS, - Address = Global.Loopback, - Port = node.PreSocksPort.Value, - }; - } - await Task.CompletedTask; - return itemSocks; - } - /// /// Remove servers with invalid test results (timeout) /// Useful for cleaning up subscription lists @@ -1326,6 +1438,10 @@ public class ConfigHandler EConfigType.TUIC => await AddTuicServer(config, profileItem, false), EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false), EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false), + EConfigType.NaiveProxy => await AddNaiveServer(config, profileItem, false), + EConfigType.Juicity => await AddJuicityServer(config, profileItem, false), + EConfigType.Brook => await AddBrookServer(config, profileItem, false), + EConfigType.Shadowquic => await AddShadowquicServer(config, profileItem, false), _ => -1, }; diff --git a/v2rayN/ServiceLib/Handler/CoreAdminHandler.cs b/v2rayN/ServiceLib/Handler/CoreAdminHandler.cs index ec99b26b..9ce5a411 100644 --- a/v2rayN/ServiceLib/Handler/CoreAdminHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreAdminHandler.cs @@ -35,7 +35,7 @@ public class CoreAdminHandler { StringBuilder sb = new(); sb.AppendLine("#!/bin/bash"); - var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}"; + var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath, coreInfo.CoreType).AppendQuotes())}"; sb.AppendLine($"sudo -S {cmdLine}"); var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true); diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index 40c5eacf..ae68ce80 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -1,3 +1,5 @@ +using ServiceLib.Services.CoreConfig.Minimal; + namespace ServiceLib.Handler; /// @@ -7,27 +9,26 @@ public class CoreConfigHandler { private static readonly string _tag = "CoreConfigHandler"; - public static async Task GenerateClientConfig(ProfileItem node, string? fileName) + public static async Task GenerateClientConfig(CoreLaunchContext context, string? fileName) { - var config = AppHandler.Instance.Config; var result = new RetResult(); - if (node.ConfigType == EConfigType.Custom) + if (context.ConfigType == EConfigType.Custom) { - result = node.CoreType switch - { - ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName), - ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName), - _ => await GenerateClientCustomConfig(node, fileName) - }; - } - else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) - { - result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node); + result = await GetCoreConfigServiceForCustom(context.CoreType).GenerateClientCustomConfig(context.Node, fileName); } else { - result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node); + try + { + result = await GetCoreConfigServiceForClientConfig(context.CoreType).GenerateClientConfigContent(context.Node); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + result.Msg = ResUI.FailedGenDefaultConfiguration; + return result; + } } if (result.Success != true) { @@ -41,65 +42,44 @@ public class CoreConfigHandler return result; } - private static async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + public static async Task GeneratePassthroughConfig(CoreLaunchContext context, string? fileName) { - var ret = new RetResult(); + var result = new RetResult(); + try { - if (node == null || fileName is null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - if (File.Exists(fileName)) - { - File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail - File.Delete(fileName); - } - - string addressFileName = node.Address; - if (!File.Exists(addressFileName)) - { - addressFileName = Utils.GetConfigPath(addressFileName); - } - if (!File.Exists(addressFileName)) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - File.Copy(addressFileName, fileName); - File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file. - - //check again - if (!File.Exists(fileName)) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); - ret.Success = true; - return await Task.FromResult(ret); + result = await GetCoreConfigServiceForPassthrough(context.CoreType).GeneratePassthroughConfig(context.Node); } catch (Exception ex) { Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; + result.Msg = ResUI.FailedGenDefaultConfiguration; + return result; } + + if (result.Success != true) + { + return result; + } + if (fileName.IsNotEmpty() && result.Data != null) + { + await File.WriteAllTextAsync(fileName, result.Data.ToString()); + } + return result; } public static async Task GenerateClientSpeedtestConfig(Config config, string fileName, List selecteds, ECoreType coreType) { var result = new RetResult(); - if (coreType == ECoreType.sing_box) + try { - result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds); + result = await GetCoreConfigServiceForMultipleSpeedtest(coreType).GenerateClientSpeedtestConfig(selecteds); } - else if (coreType == ECoreType.Xray) + catch (Exception ex) { - result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds); + Logging.SaveLog(_tag, ex); + result.Msg = ResUI.FailedGenDefaultConfiguration; + return result; } if (result.Success != true) { @@ -109,21 +89,24 @@ public class CoreConfigHandler return result; } - public static async Task GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName) + public static async Task GenerateClientSpeedtestConfig(Config config, CoreLaunchContext context, ServerTestItem testItem, string fileName) { var result = new RetResult(); var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest); var port = Utils.GetFreePort(initPort + testItem.QueueNum); testItem.Port = port; - if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) + try { - result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port); + result = await GetCoreConfigServiceForSpeedtest(context.CoreType).GenerateClientSpeedtestConfig(context.Node, port); } - else + catch (Exception ex) { - result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port); + Logging.SaveLog(_tag, ex); + result.Msg = ResUI.FailedGenDefaultConfiguration; + return result; } + if (result.Success != true) { return result; @@ -140,7 +123,7 @@ public class CoreConfigHandler { result = await new CoreConfigSingboxService(config).GenerateClientMultipleLoadConfig(selecteds); } - else + else if (coreType == ECoreType.Xray) { result = await new CoreConfigV2rayService(config).GenerateClientMultipleLoadConfig(selecteds, multipleLoad); } @@ -152,4 +135,96 @@ public class CoreConfigHandler await File.WriteAllTextAsync(fileName, result.Data.ToString()); return result; } + + private static CoreConfigServiceMinimalBase GetCoreConfigServiceForPassthrough(ECoreType coreType) + { + switch (coreType) + { + case ECoreType.sing_box: + return new CoreConfigSingboxService(AppHandler.Instance.Config); + case ECoreType.Xray: + return new CoreConfigV2rayService(AppHandler.Instance.Config); + case ECoreType.hysteria2: + return new CoreConfigHy2Service(AppHandler.Instance.Config); + case ECoreType.naiveproxy: + return new CoreConfigNaiveService(AppHandler.Instance.Config); + case ECoreType.tuic: + return new CoreConfigTuicService(AppHandler.Instance.Config); + case ECoreType.juicity: + return new CoreConfigJuicityService(AppHandler.Instance.Config); + case ECoreType.brook: + return new CoreConfigBrookService(AppHandler.Instance.Config); + case ECoreType.shadowquic: + return new CoreConfigShadowquicService(AppHandler.Instance.Config); + default: + throw new NotImplementedException($"Core type {coreType} is not implemented for passthrough configuration."); + } + } + + private static CoreConfigServiceMinimalBase GetCoreConfigServiceForSpeedtest(ECoreType coreType) + { + switch (coreType) + { + case ECoreType.sing_box: + return new CoreConfigSingboxService(AppHandler.Instance.Config); + case ECoreType.Xray: + return new CoreConfigV2rayService(AppHandler.Instance.Config); + case ECoreType.hysteria2: + return new CoreConfigHy2Service(AppHandler.Instance.Config); + case ECoreType.naiveproxy: + return new CoreConfigNaiveService(AppHandler.Instance.Config); + case ECoreType.tuic: + return new CoreConfigTuicService(AppHandler.Instance.Config); + case ECoreType.juicity: + return new CoreConfigJuicityService(AppHandler.Instance.Config); + case ECoreType.brook: + return new CoreConfigBrookService(AppHandler.Instance.Config); + case ECoreType.shadowquic: + return new CoreConfigShadowquicService(AppHandler.Instance.Config); + default: + throw new NotImplementedException($"Core type {coreType} is not implemented for passthrough configuration."); + } + } + + private static CoreConfigServiceBase GetCoreConfigServiceForMultipleSpeedtest(ECoreType coreType) + { + switch (coreType) + { + case ECoreType.sing_box: + return new CoreConfigSingboxService(AppHandler.Instance.Config); + case ECoreType.Xray: + return new CoreConfigV2rayService(AppHandler.Instance.Config); + default: + throw new NotImplementedException($"Core type {coreType} is not implemented for passthrough configuration."); + } + } + + private static CoreConfigServiceMinimalBase GetCoreConfigServiceForCustom(ECoreType coreType) + { + switch (coreType) + { + case ECoreType.mihomo: + return new CoreConfigClashService(AppHandler.Instance.Config); + case ECoreType.sing_box: + return new CoreConfigSingboxService(AppHandler.Instance.Config); + case ECoreType.hysteria2: + return new CoreConfigHy2Service(AppHandler.Instance.Config); + default: + // CoreConfigServiceMinimalBase + return new CoreConfigV2rayService(AppHandler.Instance.Config); + } + } + + private static CoreConfigServiceBase GetCoreConfigServiceForClientConfig(ECoreType coreType) + { + switch (coreType) + { + case ECoreType.sing_box: + return new CoreConfigSingboxService(AppHandler.Instance.Config); + case ECoreType.Xray: + return new CoreConfigV2rayService(AppHandler.Instance.Config); + default: + throw new NotImplementedException($"Core type {coreType} is not implemented for client configuration."); + } + } } diff --git a/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayN/ServiceLib/Handler/CoreHandler.cs index f7ad2285..01838cc1 100644 --- a/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -1,5 +1,8 @@ using System.Diagnostics; using System.Text; +using ServiceLib.Enums; +using ServiceLib.Models; +using static SQLite.SQLite3; namespace ServiceLib.Handler; @@ -71,28 +74,23 @@ public class CoreHandler return; } - var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); - var result = await CoreConfigHandler.GenerateClientConfig(node, fileName); - if (result.Success != true) + // Create launch context and configure parameters + var context = new CoreLaunchContext(node, _config); + context.AdjustForConfigType(); + + // Start main core + if (!await CoreStart(context)) { - UpdateFunc(true, result.Msg); return; } - UpdateFunc(false, $"{node.GetSummary()}"); - UpdateFunc(false, $"{Utils.GetRuntimeInfo()}"); - UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"))); - await CoreStop(); - await Task.Delay(100); - - if (Utils.IsWindows() && _config.TunModeItem.EnableTun) + // Start pre-core if needed + if (!await CoreStartPreService(context)) { - await Task.Delay(100); - await WindowsUtils.RemoveTunDevice(); + await CoreStop(); // Clean up main core if pre-core fails + return; } - await CoreStart(node); - await CoreStartPreService(node); if (_process != null) { UpdateFunc(true, $"{node.GetSummary()}"); @@ -103,7 +101,7 @@ public class CoreHandler { var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) ? ECoreType.sing_box : ECoreType.Xray; var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); - var configPath = Utils.GetBinConfigPath(fileName); + var configPath = Utils.GetBinConfigPath(fileName, coreType); var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType); UpdateFunc(false, result.Msg); if (result.Success != true) @@ -132,15 +130,17 @@ public class CoreHandler return -1; } + var context = new CoreLaunchContext(node, _config); + context.AdjustForConfigType(); + var coreType = context.CoreType; var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); - var configPath = Utils.GetBinConfigPath(fileName); - var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath); + var configPath = Utils.GetBinConfigPath(fileName, coreType); + var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, context, testItem, configPath); if (result.Success != true) { return -1; } - var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); var proc = await RunProcess(coreInfo, fileName, true, false); if (proc is null) @@ -181,43 +181,84 @@ public class CoreHandler #region Private - private async Task CoreStart(ProfileItem node) + private async Task CoreStart(CoreLaunchContext context) { - var coreType = _config.RunningCoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); - var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); + var coreType = context.SplitCore ? context.PureEndpointCore : context.CoreType; + var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName, coreType); + var result = context.SplitCore + ? await CoreConfigHandler.GeneratePassthroughConfig(context, fileName) + : await CoreConfigHandler.GenerateClientConfig(context, fileName); - var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog; - var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true); + if (result.Success != true) + { + UpdateFunc(true, result.Msg); + return false; + } + + UpdateFunc(false, $"{context.Node.GetSummary()}"); + UpdateFunc(false, $"{Utils.GetRuntimeInfo()}"); + UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"))); + + await CoreStop(); + await Task.Delay(100); + + if (Utils.IsWindows() && _config.TunModeItem.EnableTun) + { + await Task.Delay(100); + await WindowsUtils.RemoveTunDevice(); + } + + var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(context.CoreType); + var displayLog = context.Node.ConfigType != EConfigType.Custom || context.Node.DisplayLog; + var proc = await RunProcess(coreInfo, Utils.GetBinConfigFileName(Global.CoreConfigFileName, coreType), displayLog, true); + if (proc is null) { - return; + UpdateFunc(true, ResUI.FailedToRunCore); + return false; } + _process = proc; + _config.RunningCoreType = (ECoreType)(context.PreCoreType != null ? context.PreCoreType : coreType); + return true; } - private async Task CoreStartPreService(ProfileItem node) + private async Task CoreStartPreService(CoreLaunchContext context) { - if (_process != null && !_process.HasExited) + if (context.PreCoreType == null) { - var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); - var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType); - if (itemSocks != null) - { - var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box; - var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); - var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName); - if (result.Success) - { - var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(preCoreType); - var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true); - if (proc is null) - { - return; - } - _processPre = proc; - } - } + return true; // No pre-core needed, consider successful } + + var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName, (ECoreType)context.PreCoreType); + var itemSocks = new ProfileItem() + { + CoreType = context.PreCoreType, + ConfigType = EConfigType.SOCKS, + Address = Global.Loopback, + Sni = context.EnableTun && Utils.IsDomain(context.Node.Address) ? context.Node.Address : string.Empty, //Tun2SocksAddress + Port = context.PreSocksPort + }; + var itemSocksLaunch = new CoreLaunchContext(itemSocks, _config); + + var result = await CoreConfigHandler.GenerateClientConfig(itemSocksLaunch, fileName); + if (!result.Success) + { + UpdateFunc(true, result.Msg); + return false; + } + + var coreInfo = CoreInfoHandler.Instance.GetCoreInfo((ECoreType)context.PreCoreType); + var proc = await RunProcess(coreInfo, Utils.GetBinConfigFileName(Global.CorePreConfigFileName, (ECoreType)context.PreCoreType), true, true); + + if (proc is null || (_process?.HasExited == true)) + { + UpdateFunc(true, ResUI.FailedToRunCore); + return false; + } + + _processPre = proc; + return true; } private void UpdateFunc(bool notify, string msg) @@ -267,7 +308,7 @@ public class CoreHandler StartInfo = new() { FileName = fileName, - Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath), + Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath, coreInfo.CoreType).AppendQuotes() : configPath), WorkingDirectory = Utils.GetBinConfigPath(), UseShellExecute = false, RedirectStandardOutput = displayLog, diff --git a/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs b/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs index 6b7e1df2..befd80fa 100644 --- a/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs @@ -130,7 +130,7 @@ public sealed class CoreInfoHandler { CoreType = ECoreType.hysteria, CoreExes = ["hysteria"], - Arguments = "", + Arguments = "-c {0}", Url = GetCoreUrl(ECoreType.hysteria), }, @@ -180,7 +180,7 @@ public sealed class CoreInfoHandler { CoreType = ECoreType.hysteria2, CoreExes = ["hysteria-windows-amd64", "hysteria-linux-amd64", "hysteria"], - Arguments = "", + Arguments = "-c {0}", Url = GetCoreUrl(ECoreType.hysteria2), }, diff --git a/v2rayN/ServiceLib/Handler/Fmt/BrookFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/BrookFmt.cs new file mode 100644 index 00000000..4bde23b5 --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Fmt/BrookFmt.cs @@ -0,0 +1,49 @@ +using static QRCoder.PayloadGenerator; + +namespace ServiceLib.Handler.Fmt; +public class BrookFmt : BaseFmt +{ + public static ProfileItem? Resolve(string str, out string msg) + { + msg = ResUI.ConfigurationFormatIncorrect; + + var parsedUrl = Utils.TryUri(str); + if (parsedUrl == null) + { + return null; + } + + ProfileItem item = new() + { + ConfigType = EConfigType.Brook, + Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), + Address = parsedUrl.IdnHost, + Port = parsedUrl.Port, + }; + var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); + item.Id = rawUserInfo; + + var query = Utils.ParseQueryString(parsedUrl.Query); + _ = ResolveStdTransport(query, ref item); + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; + } + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var pw = item.Id; + var dicQuery = new Dictionary(); + _ = GetStdTransport(item, Global.None, ref dicQuery); + + return ToUri(EConfigType.Brook, item.Address, item.Port, pw, dicQuery, remark); + } +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs index 814d753d..41e112ff 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs @@ -19,6 +19,10 @@ public class FmtHandler EConfigType.TUIC => TuicFmt.ToUri(item), EConfigType.WireGuard => WireguardFmt.ToUri(item), EConfigType.Anytls => AnytlsFmt.ToUri(item), + EConfigType.NaiveProxy => NaiveFmt.ToUri(item), + EConfigType.Juicity => JuicityFmt.ToUri(item), + EConfigType.Brook => BrookFmt.ToUri(item), + EConfigType.Shadowquic => ShadowquicFmt.ToUri(item), _ => null, }; @@ -80,6 +84,22 @@ public class FmtHandler { return AnytlsFmt.Resolve(str, out msg); } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.NaiveProxy])) + { + return NaiveFmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Juicity])) + { + return JuicityFmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Brook])) + { + return BrookFmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Shadowquic])) + { + return ShadowquicFmt.Resolve(str, out msg); + } else { msg = ResUI.NonvmessOrssProtocol; diff --git a/v2rayN/ServiceLib/Handler/Fmt/JuicityFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/JuicityFmt.cs new file mode 100644 index 00000000..d6cda203 --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Fmt/JuicityFmt.cs @@ -0,0 +1,63 @@ +namespace ServiceLib.Handler.Fmt; + +public class JuicityFmt : BaseFmt +{ + public static ProfileItem? Resolve(string str, out string msg) + { + msg = ResUI.ConfigurationFormatIncorrect; + + ProfileItem item = new() + { + ConfigType = EConfigType.Juicity + }; + + var url = Utils.TryUri(str); + if (url == null) + { + return null; + } + + item.Address = url.IdnHost; + item.Port = url.Port; + item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + var rawUserInfo = Utils.UrlDecode(url.UserInfo); + var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2); + if (userInfoParts.Length == 2) + { + item.Id = userInfoParts.First(); + item.Security = userInfoParts.Last(); + } + + var query = Utils.ParseQueryString(url.Query); + ResolveStdTransport(query, ref item); + item.HeaderType = query["congestion_control"] ?? ""; + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; + } + + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var dicQuery = new Dictionary(); + if (item.Sni.IsNotEmpty()) + { + dicQuery.Add("sni", item.Sni); + } + if (item.Alpn.IsNotEmpty()) + { + dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); + } + dicQuery.Add("congestion_control", item.HeaderType); + + return ToUri(EConfigType.Juicity, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark); + } +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs new file mode 100644 index 00000000..95c5583d --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs @@ -0,0 +1,52 @@ +using static QRCoder.PayloadGenerator; + +namespace ServiceLib.Handler.Fmt; +public class NaiveFmt : BaseFmt +{ + public static ProfileItem? Resolve(string str, out string msg) + { + msg = ResUI.ConfigurationFormatIncorrect; + + var parsedUrl = Utils.TryUri(str); + if (parsedUrl == null) + { + return null; + } + + ProfileItem item = new() + { + ConfigType = EConfigType.NaiveProxy, + Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), + Address = parsedUrl.IdnHost, + Port = parsedUrl.Port, + }; + var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); + item.Id = rawUserInfo; + + var query = Utils.ParseQueryString(parsedUrl.Query); + _ = ResolveStdTransport(query, ref item); + + item.HeaderType = query["protocol"] ?? ""; + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; + } + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var pw = item.Id; + var dicQuery = new Dictionary(); + _ = GetStdTransport(item, Global.None, ref dicQuery); + dicQuery.Add("protocol", item.HeaderType); + + return ToUri(EConfigType.NaiveProxy, item.Address, item.Port, pw, dicQuery, remark); + } +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/ShadowquicFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/ShadowquicFmt.cs new file mode 100644 index 00000000..468923da --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Fmt/ShadowquicFmt.cs @@ -0,0 +1,63 @@ +namespace ServiceLib.Handler.Fmt; + +public class ShadowquicFmt : BaseFmt +{ + public static ProfileItem? Resolve(string str, out string msg) + { + msg = ResUI.ConfigurationFormatIncorrect; + + ProfileItem item = new() + { + ConfigType = EConfigType.Shadowquic + }; + + var url = Utils.TryUri(str); + if (url == null) + { + return null; + } + + item.Address = url.IdnHost; + item.Port = url.Port; + item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + var rawUserInfo = Utils.UrlDecode(url.UserInfo); + var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2); + if (userInfoParts.Length == 2) + { + item.Id = userInfoParts.First(); + item.Security = userInfoParts.Last(); + } + + var query = Utils.ParseQueryString(url.Query); + ResolveStdTransport(query, ref item); + item.HeaderType = query["congestion_control"] ?? ""; + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; + } + + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var dicQuery = new Dictionary(); + if (item.Sni.IsNotEmpty()) + { + dicQuery.Add("sni", item.Sni); + } + if (item.Alpn.IsNotEmpty()) + { + dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); + } + dicQuery.Add("congestion_control", item.HeaderType); + + return ToUri(EConfigType.Shadowquic, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark); + } +} diff --git a/v2rayN/ServiceLib/Models/Config.cs b/v2rayN/ServiceLib/Models/Config.cs index 15996608..c6536f15 100644 --- a/v2rayN/ServiceLib/Models/Config.cs +++ b/v2rayN/ServiceLib/Models/Config.cs @@ -48,6 +48,7 @@ public class Config public List Inbound { get; set; } public List GlobalHotkeys { get; set; } public List CoreTypeItem { get; set; } + public SplitCoreItem SplitCoreItem { get; set; } #endregion other entities } diff --git a/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayN/ServiceLib/Models/ConfigItems.cs index c6254b9f..a8aa8244 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -138,6 +138,14 @@ public class CoreTypeItem public ECoreType CoreType { get; set; } } +[Serializable] +public class SplitCoreItem +{ + public bool EnableSplitCore { get; set; } + public List SplitCoreTypes { get; set; } + public ECoreType RouteCoreType { get; set; } +} + [Serializable] public class TunModeItem { diff --git a/v2rayN/ServiceLib/Models/CoreLaunchContext.cs b/v2rayN/ServiceLib/Models/CoreLaunchContext.cs new file mode 100644 index 00000000..cf327dac --- /dev/null +++ b/v2rayN/ServiceLib/Models/CoreLaunchContext.cs @@ -0,0 +1,53 @@ +namespace ServiceLib.Models; + +/// +/// Core launch context that encapsulates all parameters required for launching +/// +public class CoreLaunchContext +{ + public ProfileItem Node { get; set; } + public bool SplitCore { get; set; } + public ECoreType CoreType { get; set; } + public ECoreType? PreCoreType { get; set; } + public ECoreType PureEndpointCore { get; set; } + public ECoreType SplitRouteCore { get; set; } + public bool EnableTun { get; set; } + public int PreSocksPort { get; set; } + public EConfigType ConfigType { get; set; } + + public CoreLaunchContext(ProfileItem node, Config config) + { + Node = node; + SplitCore = config.SplitCoreItem.EnableSplitCore; + CoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); + PureEndpointCore = AppHandler.Instance.GetSplitCoreType(node, node.ConfigType); + SplitRouteCore = config.SplitCoreItem.RouteCoreType; + EnableTun = config.TunModeItem.EnableTun; + PreSocksPort = 0; + PreCoreType = null; + ConfigType = node.ConfigType; + } + + /// + /// Adjust context parameters based on configuration type + /// + public void AdjustForConfigType() + { + (SplitCore, CoreType, PreCoreType) = AppHandler.Instance.GetCoreAndPreType(Node); + if (Node.ConfigType == EConfigType.Custom) + { + if (Node.PreSocksPort > 0) + { + PreSocksPort = Node.PreSocksPort.Value; + } + else + { + EnableTun = false; + } + } + else if (PreCoreType != null) + { + PreSocksPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.split); + } + } +} diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index ca554860..b998e8b4 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -663,6 +663,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Add [Brook] Configuration 的本地化字符串。 + /// + public static string menuAddBrookServer { + get { + return ResourceManager.GetString("menuAddBrookServer", resourceCulture); + } + } + /// /// 查找类似 Add a custom configuration Configuration 的本地化字符串。 /// @@ -690,6 +699,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Add [Juicity] Configuration 的本地化字符串。 + /// + public static string menuAddJuicityServer { + get { + return ResourceManager.GetString("menuAddJuicityServer", resourceCulture); + } + } + + /// + /// 查找类似 Add [Naive] Configuration 的本地化字符串。 + /// + public static string menuAddNaiveServer { + get { + return ResourceManager.GetString("menuAddNaiveServer", resourceCulture); + } + } + /// /// 查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。 /// @@ -717,6 +744,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Add [Shadowquic] Configuration 的本地化字符串。 + /// + public static string menuAddShadowquicServer { + get { + return ResourceManager.GetString("menuAddShadowquicServer", resourceCulture); + } + } + /// /// 查找类似 Add [Shadowsocks] Configuration 的本地化字符串。 /// @@ -2472,6 +2508,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Proxy Protocol 的本地化字符串。 + /// + public static string TbHeaderType100 { + get { + return ResourceManager.GetString("TbHeaderType100", resourceCulture); + } + } + /// /// 查找类似 Congestion control 的本地化字符串。 /// @@ -3426,6 +3471,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..aee7a852 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1404,4 +1404,28 @@ 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. + + + افزودن سرور [Brook] + + + افزودن سرور [Juicity] + + + افزودن سرور [Naive] + + + افزودن سرور [Shadowquic] + + + Proxy Protocol + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 0fc30153..167f3b88 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1404,4 +1404,28 @@ [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. + + + [Brook] szerver hozzáadása + + + [Juicity] szerver hozzáadása + + + [Naive] szerver hozzáadása + + + [Shadowquic] szerver hozzáadása + + + Proxy Protocol + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 7ecfcc98..eaf705b6 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1404,4 +1404,28 @@ 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. + + + Add [Brook] Configuration + + + Add [Juicity] Configuration + + + Add [Naive] Configuration + + + Add [Shadowquic] Configuration + + + Proxy Protocol + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 25f17e0d..bb855dfe 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1404,4 +1404,28 @@ Добавить сервер [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. + + + Добавить сервер [Brook] + + + Добавить сервер [Juicity] + + + Добавить сервер [Naive] + + + Добавить сервер [Shadowquic] + + + Proxy Protocol + \ 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..fa4f1e8b 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1401,4 +1401,28 @@ 添加 [Anytls] 配置文件 + + 启用出站核心与路由核心分离 + + + 出站与路由分离,出站与分流 Core 类型不同时,将启用两个核心 + + + 启用 Tun 时,路由 Core 为 sing-box + + + 添加 [Brook] 配置文件 + + + 添加 [Juicity] 配置文件 + + + 添加 [Naive] 配置文件 + + + 添加 [Shadowquic] 配置文件 + + + 代理协议 + \ 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..ebea5465 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1401,4 +1401,28 @@ 新增 [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. + + + 新增 [Brook] 設定檔 + + + 新增 [Juicity] 設定檔 + + + 新增 [Naive] 設定檔 + + + 新增 [Shadowquic] 設定檔 + + + Proxy Protocol + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigServiceBase.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigServiceBase.cs new file mode 100644 index 00000000..9015fbe6 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigServiceBase.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using YamlDotNet.Core.Tokens; + +namespace ServiceLib.Services.CoreConfig; + +public abstract class CoreConfigServiceMinimalBase(Config config) +{ + public virtual string _tag => GetType().Name; + protected Config _config = config; + + public virtual Task GeneratePassthroughConfig(ProfileItem node) + { + return GeneratePassthroughConfig(node, AppHandler.Instance.GetLocalPort(EInboundProtocol.split)); + } + public virtual Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + { + return GeneratePassthroughConfig(node, port); + } + protected abstract Task GeneratePassthroughConfig(ProfileItem node, int port); + public virtual async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + { + var ret = new RetResult(); + try + { + if (node == null || fileName is null) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (File.Exists(fileName)) + { + File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail + File.Delete(fileName); + } + + string addressFileName = node.Address; + if (!File.Exists(addressFileName)) + { + addressFileName = Utils.GetConfigPath(addressFileName); + } + if (!File.Exists(addressFileName)) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + File.Copy(addressFileName, fileName); + File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file. + + //check again + if (!File.Exists(fileName)) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } +} + +public abstract class CoreConfigServiceBase(Config config) : CoreConfigServiceMinimalBase(config) +{ + public abstract Task GenerateClientConfigContent(ProfileItem node); + public abstract Task GenerateClientMultipleLoadConfig(List selecteds, EMultipleLoad multipleLoad); + public virtual Task GenerateClientMultipleLoadConfig(List selecteds) + { + return GenerateClientMultipleLoadConfig(selecteds, EMultipleLoad.LeastPing); + } + public abstract Task GenerateClientSpeedtestConfig(List selecteds); +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 80b89654..51678637 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -1,25 +1,19 @@ using System.Data; +using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Reactive; +using System.Text.Json.Nodes; using DynamicData; using ServiceLib.Models; namespace ServiceLib.Services.CoreConfig; -public class CoreConfigSingboxService +public class CoreConfigSingboxService(Config config) : CoreConfigServiceBase(config) { - private Config _config; - private static readonly string _tag = "CoreConfigSingboxService"; - - public CoreConfigSingboxService(Config config) - { - _config = config; - } - #region public gen function - public async Task GenerateClientConfigContent(ProfileItem node) + public override async Task GenerateClientConfigContent(ProfileItem node) { var ret = new RetResult(); try @@ -92,7 +86,7 @@ public class CoreConfigSingboxService } } - public async Task GenerateClientSpeedtestConfig(List selecteds) + public override async Task GenerateClientSpeedtestConfig(List selecteds) { var ret = new RetResult(); try @@ -141,7 +135,7 @@ public class CoreConfigSingboxService foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) { continue; } @@ -269,7 +263,7 @@ public class CoreConfigSingboxService } } - public async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + public override async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) { var ret = new RetResult(); try @@ -340,7 +334,7 @@ public class CoreConfigSingboxService } } - public async Task GenerateClientMultipleLoadConfig(List selecteds) + public override async Task GenerateClientMultipleLoadConfig(List selecteds, EMultipleLoad multipleLoad) { var ret = new RetResult(); try @@ -377,7 +371,7 @@ public class CoreConfigSingboxService var proxyProfiles = new List(); foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) { continue; } @@ -432,7 +426,7 @@ public class CoreConfigSingboxService } } - public async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + public override async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) { var ret = new RetResult(); if (node == null || fileName is null) @@ -513,6 +507,92 @@ public class CoreConfigSingboxService } } + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) + { + var ret = new RetResult(); + try + { + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + return ret; + } + + ret.Msg = ResUI.InitialConfiguration; + + var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); + if (result.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + var singboxConfig = JsonUtils.Deserialize(result); + if (singboxConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + await GenLog(singboxConfig); + + var inbound = new Inbound4Sbox() + { + type = EInboundProtocol.mixed.ToString(), + tag = EInboundProtocol.socks.ToString(), + listen = Global.Loopback, + listen_port = port + }; + singboxConfig.inbounds = new() { inbound }; + + if (node.ConfigType == EConfigType.WireGuard) + { + singboxConfig.outbounds.RemoveAt(0); + var endpoints = new Endpoints4Sbox(); + await GenEndpoint(node, endpoints); + endpoints.tag = Global.ProxyTag; + singboxConfig.endpoints = new() { endpoints }; + } + else + { + await GenOutbound(node, singboxConfig.outbounds.First()); + } + + if (singboxConfig.endpoints == null) + { + singboxConfig.outbounds = new() { JsonUtils.DeepCopy(singboxConfig.outbounds.First()) }; + } + else + { + singboxConfig.outbounds.Clear(); + } + + await GenMoreOutbounds(node, singboxConfig); + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + var config = JsonNode.Parse(JsonUtils.Serialize(singboxConfig)).AsObject(); + + config.Remove("route"); + + ret.Data = JsonUtils.Serialize(config, true); + + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + #endregion public gen function #region private gen function @@ -1030,7 +1110,7 @@ public class CoreConfigSingboxService var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); string? prevOutboundTag = null; if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom) + && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)) { prevOutboundTag = $"prev-{Global.ProxyTag}"; var prevServer = await GenServer(prevNode); @@ -1121,7 +1201,7 @@ public class CoreConfigSingboxService { var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom) + && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -1228,7 +1308,7 @@ public class CoreConfigSingboxService // Next proxy var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom) + && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)) { nextOutbound ??= await GenServer(nextNode); nextOutbound.tag = outbound.tag; @@ -1565,7 +1645,7 @@ public class CoreConfigSingboxService var node = await AppHandler.Instance.GetProfileItemViaRemarks(outboundTag); if (node == null - || node.ConfigType == EConfigType.Custom) + || !Global.SingboxSupportConfigType.Contains(node.ConfigType)) { return Global.ProxyTag; } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index 60b4df3e..7f219032 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -1,22 +1,15 @@ +using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Text.Json.Nodes; namespace ServiceLib.Services.CoreConfig; -public class CoreConfigV2rayService +public class CoreConfigV2rayService(Config config) : CoreConfigServiceBase(config) { - private Config _config; - private static readonly string _tag = "CoreConfigV2rayService"; - - public CoreConfigV2rayService(Config config) - { - _config = config; - } - #region public gen function - public async Task GenerateClientConfigContent(ProfileItem node) + public override async Task GenerateClientConfigContent(ProfileItem node) { var ret = new RetResult(); try @@ -77,7 +70,7 @@ public class CoreConfigV2rayService } } - public async Task GenerateClientMultipleLoadConfig(List selecteds, EMultipleLoad multipleLoad) + public override async Task GenerateClientMultipleLoadConfig(List selecteds, EMultipleLoad multipleLoad) { var ret = new RetResult(); @@ -116,11 +109,7 @@ public class CoreConfigV2rayService var proxyProfiles = new List(); foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) - { - continue; - } - if (it.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) + if (!Global.XraySupportConfigType.Contains(it.ConfigType)) { continue; } @@ -206,7 +195,7 @@ public class CoreConfigV2rayService } } - public async Task GenerateClientSpeedtestConfig(List selecteds) + public override async Task GenerateClientSpeedtestConfig(List selecteds) { var ret = new RetResult(); try @@ -255,7 +244,7 @@ public class CoreConfigV2rayService foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) { continue; } @@ -358,7 +347,7 @@ public class CoreConfigV2rayService } } - public async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + public override async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) { var ret = new RetResult(); try @@ -416,6 +405,80 @@ public class CoreConfigV2rayService } } + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) + { + var ret = new RetResult(); + try + { + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (node.GetNetwork() is nameof(ETransport.quic)) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + return ret; + } + + ret.Msg = ResUI.InitialConfiguration; + + var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); + if (result.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + var v2rayConfig = JsonUtils.Deserialize(result); + if (v2rayConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + await GenLog(v2rayConfig); + + v2rayConfig.inbounds = new() { new() + { + tag = EInboundProtocol.socks.ToString(), + listen = Global.Loopback, + port = port, + protocol = EInboundProtocol.mixed.ToString(), + settings = new Inboundsettings4Ray() + { + udp = true, + auth = "noauth" + }, + } }; + + await GenOutbound(node, v2rayConfig.outbounds.First()); + + v2rayConfig.outbounds = new() { JsonUtils.DeepCopy(v2rayConfig.outbounds.First()) }; + + await GenMoreOutbounds(node, v2rayConfig); + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + + var config = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig)).AsObject(); + + config.Remove("routing"); + + ret.Data = JsonUtils.Serialize(config, true); + + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + #endregion public gen function #region private gen function @@ -638,10 +701,7 @@ public class CoreConfigV2rayService var node = await AppHandler.Instance.GetProfileItemViaRemarks(outboundTag); if (node == null - || node.ConfigType == EConfigType.Custom - || node.ConfigType == EConfigType.Hysteria2 - || node.ConfigType == EConfigType.TUIC - || node.ConfigType == EConfigType.Anytls) + || !Global.SingboxSupportConfigType.Contains(node.ConfigType)) { return Global.ProxyTag; } @@ -1220,10 +1280,7 @@ public class CoreConfigV2rayService // Previous proxy var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom - && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC - && prevNode.ConfigType != EConfigType.Anytls + && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType) && Utils.IsDomain(prevNode.Address)) { domainList.Add(prevNode.Address); @@ -1232,10 +1289,7 @@ public class CoreConfigV2rayService // Next proxy var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom - && nextNode.ConfigType != EConfigType.Hysteria2 - && nextNode.ConfigType != EConfigType.TUIC - && nextNode.ConfigType != EConfigType.Anytls + && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType) && Utils.IsDomain(nextNode.Address)) { domainList.Add(nextNode.Address); @@ -1351,10 +1405,7 @@ public class CoreConfigV2rayService var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); string? prevOutboundTag = null; if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom - && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC - && prevNode.ConfigType != EConfigType.Anytls) + && Global.XraySupportConfigType.Contains(prevNode.ConfigType)) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -1427,10 +1478,7 @@ public class CoreConfigV2rayService { var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom - && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC - && prevNode.ConfigType != EConfigType.Anytls) + && !Global.XraySupportConfigType.Contains(prevNode.ConfigType)) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -1497,10 +1545,7 @@ public class CoreConfigV2rayService // Next proxy var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom - && nextNode.ConfigType != EConfigType.Hysteria2 - && nextNode.ConfigType != EConfigType.TUIC - && nextNode.ConfigType != EConfigType.Anytls) + && !Global.XraySupportConfigType.Contains(nextNode.ConfigType)) { if (nextOutbound == null) { diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs new file mode 100644 index 00000000..b5f34dbe --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs @@ -0,0 +1,43 @@ +namespace ServiceLib.Services.CoreConfig.Minimal; +public class CoreConfigBrookService(Config config) : CoreConfigServiceMinimalBase(config) +{ + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) + { + var ret = new RetResult(); + try + { + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (node.ConfigType != EConfigType.Brook) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; + return ret; + } + + var processArgs = "client"; + + // inbound + processArgs += " --socks5 " + Global.Loopback + ":" + port.ToString(); + + // outbound + processArgs += " --server " + node.Address + ":" + node.Port; + processArgs += " --password " + node.Id; + + ret.Success = true; + ret.Data = processArgs; + + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return await Task.FromResult(ret); + } + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigClashService.cs similarity index 93% rename from v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs rename to v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigClashService.cs index 50c6df67..313dabf0 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigClashService.cs @@ -1,19 +1,21 @@ -namespace ServiceLib.Services.CoreConfig; +namespace ServiceLib.Services.CoreConfig.Minimal; /// /// Core configuration file processing class /// -public class CoreConfigClashService +public class CoreConfigClashService(Config config) : CoreConfigServiceMinimalBase(config) { - private Config _config; - private static readonly string _tag = "CoreConfigClashService"; - - public CoreConfigClashService(Config config) + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) { - _config = config; + var ret = new RetResult + { + Success = false, + Msg = ResUI.OperationFailed + }; + return await Task.FromResult(ret); } - public async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + public override async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) { var ret = new RetResult(); if (node == null || fileName is null) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs new file mode 100644 index 00000000..9f899969 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs @@ -0,0 +1,171 @@ +using System.Text.Json.Nodes; + +namespace ServiceLib.Services.CoreConfig.Minimal; +public class CoreConfigHy2Service(Config config) : CoreConfigServiceMinimalBase(config) +{ + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) + { + var ret = new RetResult(); + try + { + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (node.ConfigType != EConfigType.Hysteria2) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; + return ret; + } + + var configJsonNode = new JsonObject(); + + // inbound + configJsonNode["socks5"] = new JsonObject + { + ["listen"] = Global.Loopback + ":" + port.ToString() + }; + + // outbound + var outboundPort = string.Empty; + if (node.Ports.IsNotEmpty()) + { + outboundPort = node.Ports.Replace(':', '-'); + if (_config.HysteriaItem.HopInterval > 0) + { + configJsonNode["transport"] = new JsonObject + { + ["udp"] = new JsonObject + { + ["hopInterval"] = $"{_config.HysteriaItem.HopInterval}s" + } + }; + } + } + else + { + outboundPort = node.Port.ToString(); + } + configJsonNode["server"] = node.Address + ":" + outboundPort; + configJsonNode["auth"] = node.Id; + + if (node.Sni.IsNotEmpty()) + { + configJsonNode["tls"] = new JsonObject + { + ["sni"] = node.Sni, + ["insecure"] = node.AllowInsecure.ToLower() == "true" + }; + } + + if (node.Path.IsNotEmpty()) + { + configJsonNode["obfs"] = new JsonObject + { + ["type"] = "salamander ", + ["salamander"] = new JsonObject + { + ["password"] = node.Path + } + }; + } + + var bandwidthObject = new JsonObject(); + if (_config.HysteriaItem.UpMbps > 0) + { + bandwidthObject["up"] = $"{_config.HysteriaItem.UpMbps} mbps"; + } + if (_config.HysteriaItem.DownMbps > 0) + { + bandwidthObject["down"] = $"{_config.HysteriaItem.DownMbps} mbps"; + } + if (bandwidthObject.Count > 0) + { + configJsonNode["bandwidth"] = bandwidthObject; + } + + ret.Success = true; + ret.Data = JsonUtils.Serialize(configJsonNode, true); + + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return await Task.FromResult(ret); + } + } + + public override async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + { + var ret = new RetResult(); + try + { + if (node == null || fileName is null) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (File.Exists(fileName)) + { + File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail + File.Delete(fileName); + } + + string addressFileName = node.Address; + if (!File.Exists(addressFileName)) + { + addressFileName = Utils.GetConfigPath(addressFileName); + } + if (!File.Exists(addressFileName)) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + // Try deserializing the file to check if it is a valid JSON or YAML file + var fileContent = File.ReadAllText(addressFileName); + var jsonContent = JsonUtils.Deserialize(fileContent); + if (jsonContent != null) + { + File.Copy(addressFileName, fileName); + } + else + { + // If it's YAML, convert to JSON and write it + var yamlContent = YamlUtils.FromYaml>(fileContent); + if (yamlContent != null) + { + File.WriteAllText(fileName, JsonUtils.Serialize(yamlContent, true)); + } + else + { + ret.Msg = ResUI.FailedReadConfiguration + "2"; + return ret; + } + } + File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file. + + //check again + if (!File.Exists(fileName)) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs new file mode 100644 index 00000000..0d73147d --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs @@ -0,0 +1,65 @@ +using System.Text.Json.Nodes; + +namespace ServiceLib.Services.CoreConfig.Minimal; +public class CoreConfigJuicityService(Config config) : CoreConfigServiceMinimalBase(config) +{ + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) + { + var ret = new RetResult(); + try + { + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (node.ConfigType != EConfigType.Juicity) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; + return ret; + } + + var configJsonNode = new JsonObject(); + + // log + var logLevel = string.Empty; + switch (_config.CoreBasicItem.Loglevel) + { + case "warning": + logLevel = "warn"; + break; + default: + logLevel = _config.CoreBasicItem.Loglevel; + break; + } + configJsonNode["log_level"] = logLevel; + + // inbound + configJsonNode["listen"] = ":" + port.ToString(); + + // outbound + configJsonNode["server"] = node.Address + ":" + node.Port; + configJsonNode["uuid"] = node.Id; + configJsonNode["password"] = node.Security; + if (node.Sni.IsNotEmpty()) + { + configJsonNode["sni"] = node.Sni; + } + configJsonNode["allow_insecure"] = node.AllowInsecure == "true"; + configJsonNode["congestion_control"] = node.HeaderType; + + ret.Success = true; + ret.Data = JsonUtils.Serialize(configJsonNode, true); + + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return await Task.FromResult(ret); + } + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs new file mode 100644 index 00000000..b86e053a --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs @@ -0,0 +1,44 @@ +using System.Text.Json.Nodes; + +namespace ServiceLib.Services.CoreConfig.Minimal; +public class CoreConfigNaiveService(Config config) : CoreConfigServiceMinimalBase(config) +{ + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) + { + var ret = new RetResult(); + try + { + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (node.ConfigType != EConfigType.NaiveProxy) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; + return ret; + } + + var configJsonNode = new JsonObject(); + + // inbound + configJsonNode["listen"] = Global.SocksProtocol + Global.Loopback + ":" + port.ToString(); + + // outbound + configJsonNode["proxy"] = (node.HeaderType == "quic" ? "quic://" : Global.HttpsProtocol) + node.Id + "@" + node.Address + ":" + node.Port; + + ret.Success = true; + ret.Data = JsonUtils.Serialize(configJsonNode, true); + + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return await Task.FromResult(ret); + } + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs new file mode 100644 index 00000000..86ec9c53 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs @@ -0,0 +1,86 @@ +using System.Text.Json.Nodes; + +namespace ServiceLib.Services.CoreConfig.Minimal; +public class CoreConfigShadowquicService(Config config) : CoreConfigServiceMinimalBase(config) +{ + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) + { + var ret = new RetResult(); + try + { + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (node.ConfigType != EConfigType.Shadowquic) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; + return ret; + } + + var configYamlNode = new Dictionary(); + + // log + var logLevel = string.Empty; + switch (_config.CoreBasicItem.Loglevel) + { + case "warning": + logLevel = "warn"; + break; + default: + logLevel = _config.CoreBasicItem.Loglevel; + break; + } + configYamlNode["log-level"] = logLevel; + + // inbound + var inboundNode = new Dictionary + { + ["type"] = "socks5", + ["listen"] = Global.Loopback + ":" + port.ToString() + }; + configYamlNode["inbound"] = inboundNode; + + // outbound + var alpn = new List(); + 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"] = alpn, + ["congestion-control"] = node.HeaderType, + ["zero-rtt"] = true + }; + if (node.Sni.IsNotEmpty()) + { + outboundNode["server-name"] = node.Sni; + } + configYamlNode["outbound"] = outboundNode; + + ret.Success = true; + ret.Data = YamlUtils.ToYaml(configYamlNode); + + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return await Task.FromResult(ret); + } + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs new file mode 100644 index 00000000..0edc612c --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs @@ -0,0 +1,78 @@ +using System.Text.Json.Nodes; + +namespace ServiceLib.Services.CoreConfig.Minimal; +public class CoreConfigTuicService(Config config) : CoreConfigServiceMinimalBase(config) +{ + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) + { + var ret = new RetResult(); + try + { + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (node.ConfigType != EConfigType.TUIC) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; + return ret; + } + + var configJsonNode = new JsonObject(); + + // log + var logLevel = string.Empty; + switch (_config.CoreBasicItem.Loglevel) + { + case "warning": + logLevel = "warn"; + break; + default: + logLevel = _config.CoreBasicItem.Loglevel; + break; + } + configJsonNode["log_level"] = logLevel; + + // inbound + configJsonNode["local"] = new JsonObject + { + ["server"] = Global.Loopback + ":" + port.ToString() + }; + + // 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"] = node.HeaderType, + ["alpn"] = alpn + }; + + ret.Success = true; + ret.Data = JsonUtils.Serialize(configJsonNode, true); + + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return await Task.FromResult(ret); + } + } +} diff --git a/v2rayN/ServiceLib/Services/SpeedtestService.cs b/v2rayN/ServiceLib/Services/SpeedtestService.cs index 9c97a217..458cc334 100644 --- a/v2rayN/ServiceLib/Services/SpeedtestService.cs +++ b/v2rayN/ServiceLib/Services/SpeedtestService.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Diagnostics; +using System.Linq; using System.Net; using System.Net.Sockets; @@ -70,7 +71,7 @@ public class SpeedtestService var lstSelected = new List(); foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType))) { continue; } @@ -122,7 +123,7 @@ public class SpeedtestService List tasks = []; foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType))) { continue; } @@ -207,7 +208,7 @@ public class SpeedtestService { continue; } - if (it.ConfigType == EConfigType.Custom) + if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType))) { continue; } @@ -244,7 +245,7 @@ public class SpeedtestService UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip); continue; } - if (it.ConfigType == EConfigType.Custom) + if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType))) { continue; } @@ -358,8 +359,8 @@ public class SpeedtestService private List> GetTestBatchItem(List lstSelected, int pageSize) { List> lstTest = new(); - var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)).ToList(); - var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls).ToList(); + var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList(); + var lst2 = lstSelected.Where(t => Global.SingboxSupportConfigType.Contains(t.ConfigType) && !Global.XraySupportConfigType.Contains(t.ConfigType)).ToList(); for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++) { diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index 36e20a87..0dc991cb 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -25,6 +25,10 @@ public class MainWindowViewModel : MyReactiveObject public ReactiveCommand AddServerViaClipboardCmd { get; } public ReactiveCommand AddServerViaScanCmd { get; } public ReactiveCommand AddServerViaImageCmd { get; } + public ReactiveCommand AddBrookServerCmd { get; } + public ReactiveCommand AddJuicityServerCmd { get; } + public ReactiveCommand AddNaiveServerCmd { get; } + public ReactiveCommand AddShadowquicServerCmd { get; } //Subscription public ReactiveCommand SubSettingCmd { get; } @@ -116,6 +120,22 @@ public class MainWindowViewModel : MyReactiveObject { await AddServerAsync(true, EConfigType.Anytls); }); + AddBrookServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.Brook); + }); + AddJuicityServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.Juicity); + }); + AddNaiveServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.NaiveProxy); + }); + AddShadowquicServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.Shadowquic); + }); AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.Custom); diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index 57d8cac7..cf2252d7 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -1,6 +1,7 @@ using System.Reactive; using ReactiveUI; using ReactiveUI.Fody.Helpers; +using ServiceLib.Models; namespace ServiceLib.ViewModels; @@ -104,6 +105,21 @@ public class OptionSettingViewModel : MyReactiveObject #endregion CoreType + #region SplitCoreType + + [Reactive] public bool EnableSplitCore { get; set; } + [Reactive] public string SplitCoreType1 { get; set; } + [Reactive] public string SplitCoreType3 { get; set; } + [Reactive] public string SplitCoreType4 { get; set; } + [Reactive] public string SplitCoreType5 { get; set; } + [Reactive] public string SplitCoreType6 { get; set; } + [Reactive] public string SplitCoreType7 { get; set; } + [Reactive] public string SplitCoreType8 { get; set; } + [Reactive] public string SplitCoreType9 { get; set; } + [Reactive] public string RouteSplitCoreType { get; set; } + + #endregion SplitCoreType + public ReactiveCommand SaveCmd { get; } public OptionSettingViewModel(Func>? updateView) @@ -210,14 +226,13 @@ public class OptionSettingViewModel : MyReactiveObject #endregion Tun mode await InitCoreType(); + + await InitSplitCoreItem(); } private async Task InitCoreType() { - if (_config.CoreTypeItem == null) - { - _config.CoreTypeItem = new List(); - } + _config.CoreTypeItem ??= new List(); foreach (EConfigType it in Enum.GetValues(typeof(EConfigType))) { @@ -232,6 +247,7 @@ public class OptionSettingViewModel : MyReactiveObject CoreType = ECoreType.Xray }); } + _config.CoreTypeItem.ForEach(it => { var type = it.CoreType.ToString(); @@ -269,6 +285,87 @@ public class OptionSettingViewModel : MyReactiveObject await Task.CompletedTask; } + private async Task InitSplitCoreItem() + { + _config.SplitCoreItem ??= new() + { + EnableSplitCore = false, + SplitCoreTypes = new List(), + RouteCoreType = ECoreType.Xray + }; + + foreach (EConfigType it in Enum.GetValues(typeof(EConfigType))) + { + if (_config.SplitCoreItem.SplitCoreTypes.FindIndex(t => t.ConfigType == it) >= 0) + { + continue; + } + + if (it is EConfigType.Hysteria2 or EConfigType.TUIC) + { + _config.SplitCoreItem.SplitCoreTypes.Add(new CoreTypeItem() + { + ConfigType = it, + CoreType = ECoreType.sing_box + }); + continue; + } + if (it is EConfigType.Custom) + { + continue; + } + + _config.SplitCoreItem.SplitCoreTypes.Add(new CoreTypeItem() + { + ConfigType = it, + CoreType = ECoreType.Xray + }); + } + + EnableSplitCore = _config.SplitCoreItem.EnableSplitCore; + RouteSplitCoreType = _config.SplitCoreItem.RouteCoreType.ToString(); + + _config.SplitCoreItem.SplitCoreTypes.ForEach(it => + { + var type = it.CoreType.ToString(); + switch ((int)it.ConfigType) + { + case 1: + SplitCoreType1 = type; + break; + + case 3: + SplitCoreType3 = type; + break; + + case 4: + SplitCoreType4 = type; + break; + + case 5: + SplitCoreType5 = type; + break; + + case 6: + SplitCoreType6 = type; + break; + + case 7: + SplitCoreType7 = type; + break; + + case 8: + SplitCoreType8 = type; + break; + + case 9: + SplitCoreType9 = type; + break; + } + }); + await Task.CompletedTask; + } + private async Task SaveSettingAsync() { if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString()) @@ -362,6 +459,7 @@ public class OptionSettingViewModel : MyReactiveObject //coreType await SaveCoreType(); + await SaveSplitCoreType(); if (await ConfigHandler.SaveConfig(_config) == 0) { @@ -420,4 +518,46 @@ public class OptionSettingViewModel : MyReactiveObject } await Task.CompletedTask; } + + private async Task SaveSplitCoreType() + { + for (int k = 1; k <= _config.SplitCoreItem.SplitCoreTypes.Count; k++) + { + var item = _config.SplitCoreItem.SplitCoreTypes[k - 1]; + var type = string.Empty; + switch ((int)item.ConfigType) + { + case 1: + type = SplitCoreType1; + break; + case 3: + type = SplitCoreType3; + break; + case 4: + type = SplitCoreType4; + break; + case 5: + type = SplitCoreType5; + break; + case 6: + type = SplitCoreType6; + break; + case 7: + type = SplitCoreType7; + break; + case 8: + type = SplitCoreType8; + break; + case 9: + type = SplitCoreType9; + break; + default: + continue; + } + item.CoreType = (ECoreType)Enum.Parse(typeof(ECoreType), type); + } + _config.SplitCoreItem.RouteCoreType = (ECoreType)Enum.Parse(typeof(ECoreType), RouteSplitCoreType); + _config.SplitCoreItem.EnableSplitCore = EnableSplitCore; + await Task.CompletedTask; + } } diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index f312b890..3c25b073 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -770,7 +770,8 @@ public class ProfilesViewModel : MyReactiveObject } if (blClipboard) { - var result = await CoreConfigHandler.GenerateClientConfig(item, null); + var coreLaunchContext = new CoreLaunchContext(item, _config); + var result = await CoreConfigHandler.GenerateClientConfig(coreLaunchContext, null); if (result.Success != true) { NoticeHandler.Instance.Enqueue(result.Msg); @@ -793,7 +794,8 @@ public class ProfilesViewModel : MyReactiveObject { return; } - var result = await CoreConfigHandler.GenerateClientConfig(item, fileName); + var coreLaunchContext = new CoreLaunchContext(item, _config); + var result = await CoreConfigHandler.GenerateClientConfig(coreLaunchContext, fileName); if (result.Success != true) { NoticeHandler.Instance.Enqueue(result.Msg); diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index 56cc82a0..84f00a4d 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -553,6 +553,151 @@ Width="400" Margin="{StaticResource Margin4}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + gridHysteria2.IsVisible = true; sepa2.IsVisible = false; gridTransport.IsVisible = false; - cmbCoreType.IsEnabled = false; + cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes.AppendEmpty(); cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; break; @@ -87,11 +87,11 @@ public partial class AddServerWindow : WindowBase gridTuic.IsVisible = true; sepa2.IsVisible = false; gridTransport.IsVisible = false; - cmbCoreType.IsEnabled = false; + cmbCoreType.ItemsSource = Global.TuicCoreTypes.AppendEmpty(); cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; - cmbHeaderType8.ItemsSource = Global.TuicCongestionControls; + cmbHeaderType8.ItemsSource = Global.CongestionControls; break; case EConfigType.WireGuard: @@ -108,6 +108,49 @@ public partial class AddServerWindow : WindowBase lstStreamSecurity.Add(Global.StreamSecurityReality); cmbCoreType.IsEnabled = false; break; + + case EConfigType.NaiveProxy: + gridNaive.IsVisible = true; + sepa2.IsVisible = false; + gridTransport.IsVisible = false; + cmbAlpn.IsEnabled = false; + cmbFingerprint.IsEnabled = false; + cmbFingerprint.SelectedValue = string.Empty; + cmbCoreType.IsEnabled = false; + + cmbHeaderType100.ItemsSource = Global.NaiveProxyProtocols; + break; + + case EConfigType.Juicity: + gridJuicity.IsVisible = true; + sepa2.IsVisible = false; + gridTransport.IsVisible = false; + cmbAlpn.IsEnabled = false; + cmbFingerprint.IsEnabled = false; + cmbFingerprint.SelectedValue = string.Empty; + cmbCoreType.IsEnabled = false; + + cmbHeaderType101.ItemsSource = Global.CongestionControls; + break; + + case EConfigType.Brook: + gridBrook.IsVisible = true; + sepa2.IsVisible = false; + gridTransport.IsVisible = false; + gridTls.IsVisible = false; + cmbCoreType.IsEnabled = false; + break; + + case EConfigType.Shadowquic: + gridShadowquic.IsVisible = true; + sepa2.IsVisible = false; + gridTransport.IsVisible = false; + cmbFingerprint.IsEnabled = false; + cmbFingerprint.SelectedValue = string.Empty; + cmbCoreType.IsEnabled = false; + + cmbHeaderType103.ItemsSource = Global.CongestionControls; + break; } cmbStreamSecurity.ItemsSource = lstStreamSecurity; @@ -177,6 +220,27 @@ public partial class AddServerWindow : WindowBase case EConfigType.Anytls: this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables); break; + + case EConfigType.NaiveProxy: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId100.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType100.SelectedValue).DisposeWith(disposables); + break; + + case EConfigType.Juicity: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId101.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity101.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType101.SelectedValue).DisposeWith(disposables); + break; + + case EConfigType.Brook: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId102.Text).DisposeWith(disposables); + break; + + case EConfigType.Shadowquic: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId103.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity103.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType103.SelectedValue).DisposeWith(disposables); + break; } this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.SelectedValue).DisposeWith(disposables); diff --git a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml index af4ae529..9054ad69 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml @@ -47,6 +47,11 @@ + + + + + diff --git a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs index 2584310b..67bb67f3 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs @@ -84,6 +84,10 @@ public partial class MainWindow : WindowBase this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddBrookServerCmd, v => v.menuAddBrookServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddJuicityServerCmd, v => v.menuAddJuicityServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddNaiveServerCmd, v => v.menuAddNaiveServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddShadowquicServerCmd, v => v.menuAddShadowquicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables); 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 b/v2rayN/v2rayN/Views/AddServerWindow.xaml index acea0c28..2f5c73c0 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -736,6 +736,199 @@ Margin="{StaticResource Margin4}" Style="{StaticResource DefTextBox}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables); break; + + case EConfigType.NaiveProxy: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId100.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType100.Text).DisposeWith(disposables); + break; + + case EConfigType.Juicity: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId101.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity101.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType101.Text).DisposeWith(disposables); + break; + + case EConfigType.Brook: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId102.Text).DisposeWith(disposables); + break; + + case EConfigType.Shadowquic: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId103.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity103.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType103.Text).DisposeWith(disposables); + break; } this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.Text).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml b/v2rayN/v2rayN/Views/MainWindow.xaml index 0b8c30b3..59b6e6ed 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml +++ b/v2rayN/v2rayN/Views/MainWindow.xaml @@ -112,6 +112,23 @@ x:Name="menuAddAnytlsServer" Height="{StaticResource MenuItemHeight}" Header="{x:Static resx:ResUI.menuAddAnytlsServer}" /> + + + + + diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml.cs b/v2rayN/v2rayN/Views/MainWindow.xaml.cs index 08bdd90b..b4d41965 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/MainWindow.xaml.cs @@ -81,6 +81,10 @@ public partial class MainWindow this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddBrookServerCmd, v => v.menuAddBrookServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddJuicityServerCmd, v => v.menuAddJuicityServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddNaiveServerCmd, v => v.menuAddNaiveServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddShadowquicServerCmd, v => v.menuAddShadowquicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index 890352fc..0710d65a 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -1107,126 +1107,329 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs index c790e25d..889016bb 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs @@ -43,6 +43,17 @@ public partial class OptionSettingWindow 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; @@ -131,6 +142,18 @@ public partial class OptionSettingWindow this.Bind(ViewModel, vm => vm.CoreType6, v => v.cmbCoreType6.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.EnableSplitCore, v => v.togCoreSplit.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.RouteSplitCoreType, v => v.cmbCoreSplitRouteType.Text).DisposeWith(disposables); + + this.Bind(ViewModel, vm => vm.SplitCoreType1, v => v.cmbCoreSplitType1.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType3, v => v.cmbCoreSplitType3.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType4, v => v.cmbCoreSplitType4.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType5, v => v.cmbCoreSplitType5.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType6, v => v.cmbCoreSplitType6.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType7, v => v.cmbCoreSplitType7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType8, v => v.cmbCoreSplitType8.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType9, v => v.cmbCoreSplitType9.Text).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); }); WindowsUtils.SetDarkBorder(this, AppHandler.Instance.Config.UiItem.CurrentTheme);