mirror of
				https://github.com/2dust/v2rayN.git
				synced 2025-10-31 04:22:51 +00:00 
			
		
		
		
	Compare commits
	
		
			54 commits
		
	
	
		
			a898c57518
			...
			739a5aeaa2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 739a5aeaa2 | ||
|   | 50dcae9a04 | ||
|   | 2910f4838e | ||
|   | bee0521e47 | ||
|   | 15c3eea92e | ||
|   | 147e837bbb | ||
|   | 35f9e4bca0 | ||
|   | 8ed5c575fc | ||
|   | f6b6926419 | ||
|   | 3298286f01 | ||
|   | 8ec94fe812 | ||
|   | 372e832651 | ||
|   | 3e322b4d68 | ||
|   | 2b46857763 | ||
|   | 94afd9969a | ||
|   | db708d1e5d | ||
|   | 6462fa0905 | ||
|   | 8deb897f55 | ||
|   | e28f5ce9c2 | ||
|   | 45a2ff118e | ||
|   | daf59d01dc | ||
|   | 815a9ce0db | ||
|   | edbd8ab54a | ||
|   | d2169c914a | ||
|   | 47779e433d | ||
|   | c5f222bf58 | ||
|   | 1945d9b798 | ||
|   | 2e283eb00e | ||
|   | 00505c7c17 | ||
|   | 9229b491c5 | ||
|   | d9b0aff8da | ||
|   | 8ea39d7475 | ||
|   | f14f39f5a1 | ||
|   | 228069f499 | ||
|   | 645e4e7711 | ||
|   | d1c16ecb72 | ||
|   | dca737bdad | ||
|   | 7f5489c5f7 | ||
|   | 3be0b312d6 | ||
|   | b290a38ef4 | ||
|   | 70bdb3de16 | ||
|   | 8f9d1951a2 | ||
|   | e2faf61a8b | ||
|   | 3824879d38 | ||
|   | d54433aeb3 | ||
|   | 26b2240779 | ||
|   | 98a5caa47f | ||
|   | d77c25aef5 | ||
|   | 7affcf97b1 | ||
|   | 9e1e5eb2aa | ||
|   | d1928d80c7 | ||
|   | ea55dfb6c5 | ||
|   | b3acb89c29 | ||
|   | 922cf54d93 | 
					 59 changed files with 3968 additions and 599 deletions
				
			
		|  | @ -466,11 +466,11 @@ public class Utils | |||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     public static int GetFreePort(int defaultPort = 9090) | ||||
|     public static int GetFreePort(int defaultPort = 0) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (!Utils.PortInUse(defaultPort)) | ||||
|             if (!(defaultPort == 0 || Utils.PortInUse(defaultPort))) | ||||
|             { | ||||
|                 return defaultPort; | ||||
|             } | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -11,5 +11,10 @@ public enum EConfigType | |||
|     Hysteria2 = 7, | ||||
|     TUIC = 8, | ||||
|     WireGuard = 9, | ||||
|     HTTP = 10 | ||||
|     HTTP = 10, | ||||
|     Anytls = 11, | ||||
|     NaiveProxy = 100, | ||||
|     Juicity = 101, | ||||
|     Brook = 102, | ||||
|     Shadowquic = 103, | ||||
| } | ||||
|  |  | |||
|  | @ -9,5 +9,6 @@ public enum EInboundProtocol | |||
|     api, | ||||
|     api2, | ||||
|     mixed, | ||||
|     split, | ||||
|     speedtest = 21 | ||||
| } | ||||
|  |  | |||
|  | @ -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"; | ||||
|  | @ -169,7 +169,12 @@ public class Global | |||
|             { EConfigType.Trojan, "trojan://" }, | ||||
|             { EConfigType.Hysteria2, "hysteria2://" }, | ||||
|             { EConfigType.TUIC, "tuic://" }, | ||||
|             { EConfigType.WireGuard, "wireguard://" } | ||||
|             { EConfigType.WireGuard, "wireguard://" }, | ||||
|             { EConfigType.Anytls, "anytls://" }, | ||||
|             { EConfigType.NaiveProxy, "naive://" }, | ||||
|             { EConfigType.Juicity, "juicity://" }, | ||||
|             { EConfigType.Brook, "brook://" }, | ||||
|             { EConfigType.Shadowquic, "shadowquic://" } | ||||
|         }; | ||||
| 
 | ||||
|     public static readonly Dictionary<EConfigType, string> ProtocolTypes = new() | ||||
|  | @ -182,7 +187,12 @@ public class Global | |||
|             { EConfigType.Trojan, "trojan" }, | ||||
|             { EConfigType.Hysteria2, "hysteria2" }, | ||||
|             { EConfigType.TUIC, "tuic" }, | ||||
|             { EConfigType.WireGuard, "wireguard" } | ||||
|             { EConfigType.WireGuard, "wireguard" }, | ||||
|             { EConfigType.Anytls, "anytls" }, | ||||
|             { EConfigType.NaiveProxy, "naiveproxy" }, | ||||
|             { EConfigType.Juicity, "juicity" }, | ||||
|             { EConfigType.Brook, "brook" }, | ||||
|             { EConfigType.Shadowquic, "shadowquic" } | ||||
|         }; | ||||
| 
 | ||||
|     public static readonly List<string> VmessSecurities = | ||||
|  | @ -276,6 +286,75 @@ public class Global | |||
|             "sing_box" | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly List<string> Hysteria2CoreTypes = | ||||
|     [ | ||||
|         "sing_box", | ||||
|             "hysteria2" | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly List<string> TuicCoreTypes = | ||||
|     [ | ||||
|         "sing_box", | ||||
|             "tuic" | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly List<string> NaiveProxyCoreTypes = | ||||
|     [ | ||||
|         "naiveproxy" | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly List<string> JuicityProxyCoreTypes = | ||||
|     [ | ||||
|         "juicity" | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly List<string> BrookCoreTypes = | ||||
|     [ | ||||
|         "brook" | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly List<string> ShadowquicCoreTypes = | ||||
|     [ | ||||
|         "shadowquic" | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly List<EConfigType> SupportSplitConfigTypes = | ||||
|     [ | ||||
|         EConfigType.VMess, | ||||
|             EConfigType.VLESS, | ||||
|             EConfigType.Shadowsocks, | ||||
|             EConfigType.Trojan, | ||||
|             EConfigType.Hysteria2, | ||||
|             EConfigType.TUIC, | ||||
|             EConfigType.WireGuard, | ||||
|             EConfigType.SOCKS, | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly HashSet<EConfigType> XraySupportConfigType = | ||||
|     [ | ||||
|         EConfigType.VMess, | ||||
|             EConfigType.VLESS, | ||||
|             EConfigType.Shadowsocks, | ||||
|             EConfigType.Trojan, | ||||
|             EConfigType.WireGuard, | ||||
|             EConfigType.SOCKS, | ||||
|             EConfigType.HTTP, | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly HashSet<EConfigType> 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<string> DomainStrategies = | ||||
|     [ | ||||
|         "AsIs", | ||||
|  | @ -462,13 +541,20 @@ public class Global | |||
|             "" | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly List<string> TuicCongestionControls = | ||||
|     public static readonly List<string> CongestionControls = | ||||
|     [ | ||||
|         "cubic", | ||||
|             "new_reno", | ||||
|             "bbr" | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly List<string> NaiveProxyProtocols = | ||||
|     [ | ||||
|         "https", | ||||
|             "http", | ||||
|             "quic" | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly List<string> allowSelectType = | ||||
|     [ | ||||
|         "selector", | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -165,6 +165,13 @@ public class ConfigHandler | |||
|             config.SystemProxyItem.SystemProxyExceptions = Utils.IsWindows() ? Global.SystemProxyExceptionsWindows : Global.SystemProxyExceptionsLinux; | ||||
|         } | ||||
| 
 | ||||
|         config.SplitCoreItem ??= new() | ||||
|         { | ||||
|             EnableSplitCore = false, | ||||
|             SplitCoreTypes = new List<CoreTypeItem>(), | ||||
|             RouteCoreType = ECoreType.Xray | ||||
|         }; | ||||
| 
 | ||||
|         return config; | ||||
|     } | ||||
| 
 | ||||
|  | @ -262,6 +269,11 @@ public class ConfigHandler | |||
|             EConfigType.Hysteria2 => await AddHysteria2Server(config, item), | ||||
|             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; | ||||
|  | @ -690,7 +702,7 @@ public class ConfigHandler | |||
|     public static async Task<int> 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(); | ||||
|  | @ -723,16 +735,16 @@ public class ConfigHandler | |||
|     public static async Task<int> 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()) | ||||
|  | @ -786,6 +798,173 @@ public class ConfigHandler | |||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Add or edit a Anytls server | ||||
|     /// Validates and processes Anytls-specific settings | ||||
|     /// </summary> | ||||
|     /// <param name="config">Current configuration</param> | ||||
|     /// <param name="profileItem">Anytls profile to add</param> | ||||
|     /// <param name="toFile">Whether to save to file</param> | ||||
|     /// <returns>0 if successful, -1 if failed</returns> | ||||
|     public static async Task<int> AddAnytlsServer(Config config, ProfileItem profileItem, bool toFile = true) | ||||
|     { | ||||
|         profileItem.ConfigType = EConfigType.Anytls; | ||||
|         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 (profileItem.StreamSecurity.IsNullOrEmpty()) | ||||
|         { | ||||
|             profileItem.StreamSecurity = Global.StreamSecurity; | ||||
|         } | ||||
|         if (profileItem.Id.IsNullOrEmpty()) | ||||
|         { | ||||
|             return -1; | ||||
|         } | ||||
|         await AddServerCommon(config, profileItem, toFile); | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Add or edit a Naive server | ||||
|     /// Validates and processes Naive-specific settings | ||||
|     /// </summary> | ||||
|     /// <param name="config">Current configuration</param> | ||||
|     /// <param name="profileItem">Naive profile to add</param> | ||||
|     /// <param name="toFile">Whether to save to file</param> | ||||
|     /// <returns>0 if successful, -1 if failed</returns> | ||||
|     public static async Task<int> 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; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Add or edit a Juicity server | ||||
|     /// Validates and processes Juicity-specific settings | ||||
|     /// </summary> | ||||
|     /// <param name="config">Current configuration</param> | ||||
|     /// <param name="profileItem">Juicity profile to add</param> | ||||
|     /// <param name="toFile">Whether to save to file</param> | ||||
|     /// <returns>0 if successful, -1 if failed</returns> | ||||
|     public static async Task<int> 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; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Add or edit a Brook server | ||||
|     /// Validates and processes Brook-specific settings | ||||
|     /// </summary> | ||||
|     /// <param name="config">Current configuration</param> | ||||
|     /// <param name="profileItem">Brook profile to add</param> | ||||
|     /// <param name="toFile">Whether to save to file</param> | ||||
|     /// <returns>0 if successful, -1 if failed</returns> | ||||
|     public static async Task<int> 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; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Add or edit a Shadowquic server | ||||
|     /// Validates and processes Shadowquic-specific settings | ||||
|     /// </summary> | ||||
|     /// <param name="config">Current configuration</param> | ||||
|     /// <param name="profileItem">Shadowquic profile to add</param> | ||||
|     /// <param name="toFile">Whether to save to file</param> | ||||
|     /// <returns>0 if successful, -1 if failed</returns> | ||||
|     public static async Task<int> 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; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Sort the server list by the specified column | ||||
|     /// Updates the sort order in the profile extension data | ||||
|  | @ -1160,43 +1339,6 @@ public class ConfigHandler | |||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 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 | ||||
|     /// </summary> | ||||
|     /// <param name="config">Current configuration</param> | ||||
|     /// <param name="node">Server node that might need pre-SOCKS</param> | ||||
|     /// <param name="coreType">Core type being used</param> | ||||
|     /// <returns>A SOCKS profile item or null if not needed</returns> | ||||
|     public static async Task<ProfileItem?> 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; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Remove servers with invalid test results (timeout) | ||||
|     /// Useful for cleaning up subscription lists | ||||
|  | @ -1295,6 +1437,11 @@ public class ConfigHandler | |||
|                 EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false), | ||||
|                 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, | ||||
|             }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,5 @@ | |||
| using ServiceLib.Services.CoreConfig.Minimal; | ||||
| 
 | ||||
| namespace ServiceLib.Handler; | ||||
| 
 | ||||
| /// <summary> | ||||
|  | @ -7,27 +9,26 @@ public class CoreConfigHandler | |||
| { | ||||
|     private static readonly string _tag = "CoreConfigHandler"; | ||||
| 
 | ||||
|     public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName) | ||||
|     public static async Task<RetResult> 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<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName) | ||||
|     public static async Task<RetResult> 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<RetResult> GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> 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<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName) | ||||
|     public static async Task<RetResult> 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."); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,8 @@ | |||
| using System.Diagnostics; | ||||
| using System.Text; | ||||
| using ServiceLib.Enums; | ||||
| using ServiceLib.Models; | ||||
| using static SQLite.SQLite3; | ||||
| 
 | ||||
| namespace ServiceLib.Handler; | ||||
| 
 | ||||
|  | @ -25,8 +28,6 @@ public class CoreHandler | |||
|         Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); | ||||
|         Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); | ||||
|         Environment.SetEnvironmentVariable(Global.XrayLocalCert, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); | ||||
|         // TODO Temporary addition to support proper use of sing-box v1.12 | ||||
|         Environment.SetEnvironmentVariable("ENABLE_DEPRECATED_SPECIAL_OUTBOUNDS", "true", EnvironmentVariableTarget.Process); | ||||
| 
 | ||||
|         //Copy the bin folder to the storage location (for init) | ||||
|         if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1") | ||||
|  | @ -73,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,9 +99,9 @@ public class CoreHandler | |||
| 
 | ||||
|     public async Task<int> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds) | ||||
|     { | ||||
|         var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC) ? ECoreType.sing_box : ECoreType.Xray; | ||||
|         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) | ||||
|  | @ -134,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) | ||||
|  | @ -183,43 +181,84 @@ public class CoreHandler | |||
| 
 | ||||
|     #region Private | ||||
| 
 | ||||
|     private async Task CoreStart(ProfileItem node) | ||||
|     private async Task<bool> 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); | ||||
| 
 | ||||
|         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); | ||||
|          | ||||
|         var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog; | ||||
|         var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, 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<bool> 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; | ||||
|             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) | ||||
|  | @ -269,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, | ||||
|  |  | |||
|  | @ -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), | ||||
|                 }, | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										49
									
								
								v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | |||
| using static QRCoder.PayloadGenerator; | ||||
| 
 | ||||
| namespace ServiceLib.Handler.Fmt; | ||||
| public class AnytlsFmt : 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.Anytls, | ||||
|             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<string, string>(); | ||||
|         _ = GetStdTransport(item, Global.None, ref dicQuery); | ||||
| 
 | ||||
|         return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										49
									
								
								v2rayN/ServiceLib/Handler/Fmt/BrookFmt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								v2rayN/ServiceLib/Handler/Fmt/BrookFmt.cs
									
									
									
									
									
										Normal file
									
								
							|  | @ -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<string, string>(); | ||||
|         _ = GetStdTransport(item, Global.None, ref dicQuery); | ||||
| 
 | ||||
|         return ToUri(EConfigType.Brook, item.Address, item.Port, pw, dicQuery, remark); | ||||
|     } | ||||
| } | ||||
|  | @ -18,6 +18,11 @@ public class FmtHandler | |||
|                 EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item), | ||||
|                 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, | ||||
|             }; | ||||
| 
 | ||||
|  | @ -75,6 +80,26 @@ public class FmtHandler | |||
|             { | ||||
|                 return WireguardFmt.Resolve(str, out msg); | ||||
|             } | ||||
|             else if (str.StartsWith(Global.ProtocolShares[EConfigType.Anytls])) | ||||
|             { | ||||
|                 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; | ||||
|  |  | |||
							
								
								
									
										63
									
								
								v2rayN/ServiceLib/Handler/Fmt/JuicityFmt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								v2rayN/ServiceLib/Handler/Fmt/JuicityFmt.cs
									
									
									
									
									
										Normal file
									
								
							|  | @ -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<string, string>(); | ||||
|         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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										52
									
								
								v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs
									
									
									
									
									
										Normal file
									
								
							|  | @ -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<string, string>(); | ||||
|         _ = GetStdTransport(item, Global.None, ref dicQuery); | ||||
|         dicQuery.Add("protocol", item.HeaderType); | ||||
| 
 | ||||
|         return ToUri(EConfigType.NaiveProxy, item.Address, item.Port, pw, dicQuery, remark); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										63
									
								
								v2rayN/ServiceLib/Handler/Fmt/ShadowquicFmt.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								v2rayN/ServiceLib/Handler/Fmt/ShadowquicFmt.cs
									
									
									
									
									
										Normal file
									
								
							|  | @ -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<string, string>(); | ||||
|         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); | ||||
|     } | ||||
| } | ||||
|  | @ -48,6 +48,7 @@ public class Config | |||
|     public List<InItem> Inbound { get; set; } | ||||
|     public List<KeyEventItem> GlobalHotkeys { get; set; } | ||||
|     public List<CoreTypeItem> CoreTypeItem { get; set; } | ||||
|     public SplitCoreItem SplitCoreItem { get; set; } | ||||
| 
 | ||||
|     #endregion other entities | ||||
| } | ||||
|  |  | |||
|  | @ -138,6 +138,14 @@ public class CoreTypeItem | |||
|     public ECoreType CoreType { get; set; } | ||||
| } | ||||
| 
 | ||||
| [Serializable] | ||||
| public class SplitCoreItem | ||||
| { | ||||
|     public bool EnableSplitCore { get; set; } | ||||
|     public List<CoreTypeItem> SplitCoreTypes { get; set; } | ||||
|     public ECoreType RouteCoreType { get; set; } | ||||
| } | ||||
| 
 | ||||
| [Serializable] | ||||
| public class TunModeItem | ||||
| { | ||||
|  |  | |||
							
								
								
									
										53
									
								
								v2rayN/ServiceLib/Models/CoreLaunchContext.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								v2rayN/ServiceLib/Models/CoreLaunchContext.cs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | |||
| namespace ServiceLib.Models; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// Core launch context that encapsulates all parameters required for launching | ||||
| /// </summary> | ||||
| 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; | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Adjust context parameters based on configuration type | ||||
|     /// </summary> | ||||
|     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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,3 +1,5 @@ | |||
| using System.Text.Json.Serialization; | ||||
| 
 | ||||
| namespace ServiceLib.Models; | ||||
| 
 | ||||
| public class SingboxConfig | ||||
|  | @ -6,6 +8,7 @@ public class SingboxConfig | |||
|     public Dns4Sbox? dns { get; set; } | ||||
|     public List<Inbound4Sbox> inbounds { get; set; } | ||||
|     public List<Outbound4Sbox> outbounds { get; set; } | ||||
|     public List<Endpoints4Sbox>? endpoints { get; set; } | ||||
|     public Route4Sbox route { get; set; } | ||||
|     public Experimental4Sbox? experimental { get; set; } | ||||
| } | ||||
|  | @ -29,7 +32,6 @@ public class Dns4Sbox | |||
|     public bool? independent_cache { get; set; } | ||||
|     public bool? reverse_mapping { get; set; } | ||||
|     public string? client_subnet { get; set; } | ||||
|     public Fakeip4Sbox? fakeip { get; set; } | ||||
| } | ||||
| 
 | ||||
| public class Route4Sbox | ||||
|  | @ -37,6 +39,7 @@ public class Route4Sbox | |||
|     public bool? auto_detect_interface { get; set; } | ||||
|     public List<Rule4Sbox> rules { get; set; } | ||||
|     public List<Ruleset4Sbox>? rule_set { get; set; } | ||||
|     public string? final { get; set; } | ||||
| } | ||||
| 
 | ||||
| [Serializable] | ||||
|  | @ -49,6 +52,7 @@ public class Rule4Sbox | |||
|     public string? mode { get; set; } | ||||
|     public bool? ip_is_private { get; set; } | ||||
|     public string? client_subnet { get; set; } | ||||
|     public int? rewrite_ttl { get; set; } | ||||
|     public bool? invert { get; set; } | ||||
|     public string? clash_mode { get; set; } | ||||
|     public List<string>? inbound { get; set; } | ||||
|  | @ -67,6 +71,27 @@ public class Rule4Sbox | |||
|     public List<string>? process_name { get; set; } | ||||
|     public List<string>? rule_set { get; set; } | ||||
|     public List<Rule4Sbox>? rules { get; set; } | ||||
|     public string? action { get; set; } | ||||
|     public string? strategy { get; set; } | ||||
|     public List<string>? sniffer { get; set; } | ||||
|     public string? rcode { get; set; } | ||||
|     public List<object>? query_type { get; set; } | ||||
|     public List<string>? answer { get; set; } | ||||
|     public List<string>? ns { get; set; } | ||||
|     public List<string>? extra { get; set; } | ||||
|     public string? method { get; set; } | ||||
|     public bool? no_drop { get; set; } | ||||
|     public bool? source_ip_is_private { get; set; } | ||||
|     public bool? ip_accept_any { get; set; } | ||||
|     public int? source_port { get; set; } | ||||
|     public List<string>? source_port_range { get; set; } | ||||
|     public List<string>? network_type { get; set; } | ||||
|     public bool? network_is_expensive { get; set; } | ||||
|     public bool? network_is_constrained { get; set; } | ||||
|     public List<string>? wifi_ssid { get; set; } | ||||
|     public List<string>? wifi_bssid { get; set; } | ||||
|     public bool? rule_set_ip_cidr_match_source { get; set; } | ||||
|     public bool? rule_set_ip_cidr_accept_empty { get; set; } | ||||
| } | ||||
| 
 | ||||
| [Serializable] | ||||
|  | @ -76,7 +101,6 @@ public class Inbound4Sbox | |||
|     public string tag { get; set; } | ||||
|     public string listen { get; set; } | ||||
|     public int? listen_port { get; set; } | ||||
|     public string? domain_strategy { get; set; } | ||||
|     public string interface_name { get; set; } | ||||
|     public List<string>? address { get; set; } | ||||
|     public int? mtu { get; set; } | ||||
|  | @ -84,8 +108,6 @@ public class Inbound4Sbox | |||
|     public bool? strict_route { get; set; } | ||||
|     public bool? endpoint_independent_nat { get; set; } | ||||
|     public string? stack { get; set; } | ||||
|     public bool? sniff { get; set; } | ||||
|     public bool? sniff_override_destination { get; set; } | ||||
|     public List<User4Sbox> users { get; set; } | ||||
| } | ||||
| 
 | ||||
|  | @ -95,10 +117,8 @@ public class User4Sbox | |||
|     public string password { get; set; } | ||||
| } | ||||
| 
 | ||||
| public class Outbound4Sbox | ||||
| public class Outbound4Sbox : BaseServer4Sbox | ||||
| { | ||||
|     public string type { get; set; } | ||||
|     public string tag { get; set; } | ||||
|     public string? server { get; set; } | ||||
|     public int? server_port { get; set; } | ||||
|     public List<string>? server_ports { get; set; } | ||||
|  | @ -113,7 +133,6 @@ public class Outbound4Sbox | |||
|     public int? recv_window_conn { get; set; } | ||||
|     public int? recv_window { get; set; } | ||||
|     public bool? disable_mtu_discovery { get; set; } | ||||
|     public string? detour { get; set; } | ||||
|     public string? method { get; set; } | ||||
|     public string? username { get; set; } | ||||
|     public string? password { get; set; } | ||||
|  | @ -121,21 +140,36 @@ public class Outbound4Sbox | |||
|     public string? version { get; set; } | ||||
|     public string? network { get; set; } | ||||
|     public string? packet_encoding { get; set; } | ||||
|     public List<string>? local_address { get; set; } | ||||
|     public string? private_key { get; set; } | ||||
|     public string? peer_public_key { get; set; } | ||||
|     public List<int>? reserved { get; set; } | ||||
|     public int? mtu { get; set; } | ||||
|     public string? plugin { get; set; } | ||||
|     public string? plugin_opts { get; set; } | ||||
|     public Tls4Sbox? tls { get; set; } | ||||
|     public Multiplex4Sbox? multiplex { get; set; } | ||||
|     public Transport4Sbox? transport { get; set; } | ||||
|     public HyObfs4Sbox? obfs { get; set; } | ||||
|     public List<string>? outbounds { get; set; } | ||||
|     public bool? interrupt_exist_connections { get; set; } | ||||
| } | ||||
| 
 | ||||
| public class Endpoints4Sbox : BaseServer4Sbox | ||||
| { | ||||
|     public bool? system { get; set; } | ||||
|     public string? name { get; set; } | ||||
|     public int? mtu { get; set; } | ||||
|     public List<string> address { get; set; } | ||||
|     public string private_key { get; set; } | ||||
|     public int? listen_port { get; set; } | ||||
|     public string? udp_timeout { get; set; } | ||||
|     public int? workers { get; set; } | ||||
|     public List<Peer4Sbox> peers { get; set; } | ||||
| } | ||||
| 
 | ||||
| public class Peer4Sbox | ||||
| { | ||||
|     public string address { get; set; } | ||||
|     public int port { get; set; } | ||||
|     public string public_key { get; set; } | ||||
|     public string? pre_shared_key { get; set; } | ||||
|     public List<string> allowed_ips { get; set; } | ||||
|     public int? persistent_keepalive_interval { get; set; } | ||||
|     public List<int> reserved { get; set; } | ||||
| } | ||||
| 
 | ||||
| public class Tls4Sbox | ||||
| { | ||||
|     public bool enabled { get; set; } | ||||
|  | @ -191,15 +225,25 @@ public class HyObfs4Sbox | |||
|     public string? password { get; set; } | ||||
| } | ||||
| 
 | ||||
| public class Server4Sbox | ||||
| public class Server4Sbox : BaseServer4Sbox | ||||
| { | ||||
|     public string? tag { get; set; } | ||||
|     public string? inet4_range { get; set; } | ||||
|     public string? inet6_range { get; set; } | ||||
|     public string? client_subnet { get; set; } | ||||
|     public string? server { get; set; } | ||||
|     public new string? domain_resolver { get; set; } | ||||
|     [JsonPropertyName("interface")] public string? Interface { get; set; } | ||||
|     public int? server_port { get; set; } | ||||
|     public string? path { get; set; } | ||||
|     public Headers4Sbox? headers { get; set; } | ||||
|     // public List<string>? path { get; set; } // hosts | ||||
|     public Dictionary<string, object>? predefined { get; set; } | ||||
|     // Deprecated | ||||
|     public string? address { get; set; } | ||||
|     public string? address_resolver { get; set; } | ||||
|     public string? address_strategy { get; set; } | ||||
|     public string? strategy { get; set; } | ||||
|     public string? detour { get; set; } | ||||
|     public string? client_subnet { get; set; } | ||||
|     // Deprecated End | ||||
| } | ||||
| 
 | ||||
| public class Experimental4Sbox | ||||
|  | @ -229,13 +273,6 @@ public class Stats4Sbox | |||
|     public List<string>? users { get; set; } | ||||
| } | ||||
| 
 | ||||
| public class Fakeip4Sbox | ||||
| { | ||||
|     public bool enabled { get; set; } | ||||
|     public string inet4_range { get; set; } | ||||
|     public string inet6_range { get; set; } | ||||
| } | ||||
| 
 | ||||
| public class CacheFile4Sbox | ||||
| { | ||||
|     public bool enabled { get; set; } | ||||
|  | @ -254,3 +291,33 @@ public class Ruleset4Sbox | |||
|     public string? download_detour { get; set; } | ||||
|     public string? update_interval { get; set; } | ||||
| } | ||||
| 
 | ||||
| public abstract class DialFields4Sbox | ||||
| { | ||||
|     public string? detour { get; set; } | ||||
|     public string? bind_interface { get; set; } | ||||
|     public string? inet4_bind_address { get; set; } | ||||
|     public string? inet6_bind_address { get; set; } | ||||
|     public int? routing_mark { get; set; } | ||||
|     public bool? reuse_addr { get; set; } | ||||
|     public string? netns { get; set; } | ||||
|     public string? connect_timeout { get; set; } | ||||
|     public bool? tcp_fast_open { get; set; } | ||||
|     public bool? tcp_multi_path { get; set; } | ||||
|     public bool? udp_fragment { get; set; } | ||||
|     public Rule4Sbox? domain_resolver { get; set; } // or string | ||||
|     public string? network_strategy { get; set; } | ||||
|     public List<string>? network_type { get; set; } | ||||
|     public List<string>? fallback_network_type { get; set; } | ||||
|     public string? fallback_delay { get; set; } | ||||
|     public Tls4Sbox? tls { get; set; } | ||||
|     public Multiplex4Sbox? multiplex { get; set; } | ||||
|     public Transport4Sbox? transport { get; set; } | ||||
|     public HyObfs4Sbox? obfs { get; set; } | ||||
| } | ||||
| 
 | ||||
| public abstract class BaseServer4Sbox : DialFields4Sbox | ||||
| { | ||||
|     public string type { get; set; } | ||||
|     public string tag { get; set; } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										81
									
								
								v2rayN/ServiceLib/Resx/ResUI.Designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										81
									
								
								v2rayN/ServiceLib/Resx/ResUI.Designer.cs
									
									
									
										generated
									
									
									
								
							|  | @ -654,6 +654,24 @@ namespace ServiceLib.Resx { | |||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Add [Anytls] Configuration 的本地化字符串。 | ||||
|         /// </summary> | ||||
|         public static string menuAddAnytlsServer { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("menuAddAnytlsServer", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Add [Brook] Configuration 的本地化字符串。 | ||||
|         /// </summary> | ||||
|         public static string menuAddBrookServer { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("menuAddBrookServer", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Add a custom configuration Configuration 的本地化字符串。 | ||||
|         /// </summary> | ||||
|  | @ -681,6 +699,24 @@ namespace ServiceLib.Resx { | |||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Add [Juicity] Configuration 的本地化字符串。 | ||||
|         /// </summary> | ||||
|         public static string menuAddJuicityServer { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("menuAddJuicityServer", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Add [Naive] Configuration 的本地化字符串。 | ||||
|         /// </summary> | ||||
|         public static string menuAddNaiveServer { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("menuAddNaiveServer", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。 | ||||
|         /// </summary> | ||||
|  | @ -708,6 +744,15 @@ namespace ServiceLib.Resx { | |||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Add [Shadowquic] Configuration 的本地化字符串。 | ||||
|         /// </summary> | ||||
|         public static string menuAddShadowquicServer { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("menuAddShadowquicServer", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Add [Shadowsocks] Configuration 的本地化字符串。 | ||||
|         /// </summary> | ||||
|  | @ -2463,6 +2508,15 @@ namespace ServiceLib.Resx { | |||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Proxy Protocol 的本地化字符串。 | ||||
|         /// </summary> | ||||
|         public static string TbHeaderType100 { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("TbHeaderType100", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Congestion control 的本地化字符串。 | ||||
|         /// </summary> | ||||
|  | @ -3417,6 +3471,33 @@ namespace ServiceLib.Resx { | |||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. 的本地化字符串。 | ||||
|         /// </summary> | ||||
|         public static string TbSettingsSplitCoreDoc1 { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("TbSettingsSplitCoreDoc1", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Routing Core defaults to sing-box when Tun is enabled. 的本地化字符串。 | ||||
|         /// </summary> | ||||
|         public static string TbSettingsSplitCoreDoc2 { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("TbSettingsSplitCoreDoc2", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 Enable separation of outbound and routing cores 的本地化字符串。 | ||||
|         /// </summary> | ||||
|         public static string TbSettingsSplitCoreEnable { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("TbSettingsSplitCoreEnable", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   查找类似 sing-box ruleset files source (optional) 的本地化字符串。 | ||||
|         /// </summary> | ||||
|  |  | |||
|  | @ -1401,4 +1401,31 @@ | |||
|   <data name="TbMldsa65Verify" xml:space="preserve"> | ||||
|     <value>Mldsa65Verify</value> | ||||
|   </data> | ||||
|   <data name="menuAddAnytlsServer" xml:space="preserve"> | ||||
|     <value>Add [Anytls] Configuration</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreEnable" xml:space="preserve"> | ||||
|     <value>Enable separation of outbound and routing cores</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreDoc1" xml:space="preserve"> | ||||
|     <value>Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated.</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreDoc2" xml:space="preserve"> | ||||
|     <value>Routing Core defaults to sing-box when Tun is enabled.</value> | ||||
|   </data> | ||||
|   <data name="menuAddBrookServer" xml:space="preserve"> | ||||
|     <value>افزودن سرور [Brook]</value> | ||||
|   </data> | ||||
|   <data name="menuAddJuicityServer" xml:space="preserve"> | ||||
|     <value>افزودن سرور [Juicity]</value> | ||||
|   </data> | ||||
|   <data name="menuAddNaiveServer" xml:space="preserve"> | ||||
|     <value>افزودن سرور [Naive]</value> | ||||
|   </data> | ||||
|   <data name="menuAddShadowquicServer" xml:space="preserve"> | ||||
|     <value>افزودن سرور [Shadowquic]</value> | ||||
|   </data> | ||||
|   <data name="TbHeaderType100" xml:space="preserve"> | ||||
|     <value>Proxy Protocol</value> | ||||
|   </data> | ||||
| </root> | ||||
|  | @ -1401,4 +1401,31 @@ | |||
|   <data name="TbMldsa65Verify" xml:space="preserve"> | ||||
|     <value>Mldsa65Verify</value> | ||||
|   </data> | ||||
|   <data name="menuAddAnytlsServer" xml:space="preserve"> | ||||
|     <value>[Anytls] konfiguráció hozzáadása</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreEnable" xml:space="preserve"> | ||||
|     <value>Enable separation of outbound and routing cores</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreDoc1" xml:space="preserve"> | ||||
|     <value>Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated.</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreDoc2" xml:space="preserve"> | ||||
|     <value>Routing Core defaults to sing-box when Tun is enabled.</value> | ||||
|   </data> | ||||
|   <data name="menuAddBrookServer" xml:space="preserve"> | ||||
|     <value>[Brook] szerver hozzáadása</value> | ||||
|   </data> | ||||
|   <data name="menuAddJuicityServer" xml:space="preserve"> | ||||
|     <value>[Juicity] szerver hozzáadása</value> | ||||
|   </data> | ||||
|   <data name="menuAddNaiveServer" xml:space="preserve"> | ||||
|     <value>[Naive] szerver hozzáadása</value> | ||||
|   </data> | ||||
|   <data name="menuAddShadowquicServer" xml:space="preserve"> | ||||
|     <value>[Shadowquic] szerver hozzáadása</value> | ||||
|   </data> | ||||
|   <data name="TbHeaderType100" xml:space="preserve"> | ||||
|     <value>Proxy Protocol</value> | ||||
|   </data> | ||||
| </root> | ||||
|  | @ -1401,4 +1401,31 @@ | |||
|   <data name="TbMldsa65Verify" xml:space="preserve"> | ||||
|     <value>Mldsa65Verify</value> | ||||
|   </data> | ||||
|   <data name="menuAddAnytlsServer" xml:space="preserve"> | ||||
|     <value>Add [Anytls] Configuration</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreEnable" xml:space="preserve"> | ||||
|     <value>Enable separation of outbound and routing cores</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreDoc1" xml:space="preserve"> | ||||
|     <value>Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated.</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreDoc2" xml:space="preserve"> | ||||
|     <value>Routing Core defaults to sing-box when Tun is enabled.</value> | ||||
|   </data> | ||||
|   <data name="menuAddBrookServer" xml:space="preserve"> | ||||
|     <value>Add [Brook] Configuration</value> | ||||
|   </data> | ||||
|   <data name="menuAddJuicityServer" xml:space="preserve"> | ||||
|     <value>Add [Juicity] Configuration</value> | ||||
|   </data> | ||||
|   <data name="menuAddNaiveServer" xml:space="preserve"> | ||||
|     <value>Add [Naive] Configuration</value> | ||||
|   </data> | ||||
|   <data name="menuAddShadowquicServer" xml:space="preserve"> | ||||
|     <value>Add [Shadowquic] Configuration</value> | ||||
|   </data> | ||||
|   <data name="TbHeaderType100" xml:space="preserve"> | ||||
|     <value>Proxy Protocol</value> | ||||
|   </data> | ||||
| </root> | ||||
|  | @ -1401,4 +1401,31 @@ | |||
|   <data name="TbMldsa65Verify" xml:space="preserve"> | ||||
|     <value>Mldsa65Verify</value> | ||||
|   </data> | ||||
|   <data name="menuAddAnytlsServer" xml:space="preserve"> | ||||
|     <value>Добавить сервер [Anytls]</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreEnable" xml:space="preserve"> | ||||
|     <value>Enable separation of outbound and routing cores</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreDoc1" xml:space="preserve"> | ||||
|     <value>Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated.</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreDoc2" xml:space="preserve"> | ||||
|     <value>Routing Core defaults to sing-box when Tun is enabled.</value> | ||||
|   </data> | ||||
|   <data name="menuAddBrookServer" xml:space="preserve"> | ||||
|     <value>Добавить сервер [Brook]</value> | ||||
|   </data> | ||||
|   <data name="menuAddJuicityServer" xml:space="preserve"> | ||||
|     <value>Добавить сервер [Juicity]</value> | ||||
|   </data> | ||||
|   <data name="menuAddNaiveServer" xml:space="preserve"> | ||||
|     <value>Добавить сервер [Naive]</value> | ||||
|   </data> | ||||
|   <data name="menuAddShadowquicServer" xml:space="preserve"> | ||||
|     <value>Добавить сервер [Shadowquic]</value> | ||||
|   </data> | ||||
|   <data name="TbHeaderType100" xml:space="preserve"> | ||||
|     <value>Proxy Protocol</value> | ||||
|   </data> | ||||
| </root> | ||||
|  | @ -1398,4 +1398,31 @@ | |||
|   <data name="TbMldsa65Verify" xml:space="preserve"> | ||||
|     <value>Mldsa65Verify</value> | ||||
|   </data> | ||||
|   <data name="menuAddAnytlsServer" xml:space="preserve"> | ||||
|     <value>添加 [Anytls] 配置文件</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreEnable" xml:space="preserve"> | ||||
|     <value>启用出站核心与路由核心分离</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreDoc1" xml:space="preserve"> | ||||
|     <value>出站与路由分离,出站与分流 Core 类型不同时,将启用两个核心</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreDoc2" xml:space="preserve"> | ||||
|     <value>启用 Tun 时,路由 Core 为 sing-box</value> | ||||
|   </data> | ||||
|   <data name="menuAddBrookServer" xml:space="preserve"> | ||||
|     <value>添加 [Brook] 配置文件</value> | ||||
|   </data> | ||||
|   <data name="menuAddJuicityServer" xml:space="preserve"> | ||||
|     <value>添加 [Juicity] 配置文件</value> | ||||
|   </data> | ||||
|   <data name="menuAddNaiveServer" xml:space="preserve"> | ||||
|     <value>添加 [Naive] 配置文件</value> | ||||
|   </data> | ||||
|   <data name="menuAddShadowquicServer" xml:space="preserve"> | ||||
|     <value>添加 [Shadowquic] 配置文件</value> | ||||
|   </data> | ||||
|   <data name="TbHeaderType100" xml:space="preserve"> | ||||
|     <value>代理协议</value> | ||||
|   </data> | ||||
| </root> | ||||
|  | @ -1398,4 +1398,31 @@ | |||
|   <data name="TbMldsa65Verify" xml:space="preserve"> | ||||
|     <value>Mldsa65Verify</value> | ||||
|   </data> | ||||
|   <data name="menuAddAnytlsServer" xml:space="preserve"> | ||||
|     <value>新增 [Anytls] 設定檔</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreEnable" xml:space="preserve"> | ||||
|     <value>Enable separation of outbound and routing cores</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreDoc1" xml:space="preserve"> | ||||
|     <value>Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated.</value> | ||||
|   </data> | ||||
|   <data name="TbSettingsSplitCoreDoc2" xml:space="preserve"> | ||||
|     <value>Routing Core defaults to sing-box when Tun is enabled.</value> | ||||
|   </data> | ||||
|   <data name="menuAddBrookServer" xml:space="preserve"> | ||||
|     <value>新增 [Brook] 設定檔</value> | ||||
|   </data> | ||||
|   <data name="menuAddJuicityServer" xml:space="preserve"> | ||||
|     <value>新增 [Juicity] 設定檔</value> | ||||
|   </data> | ||||
|   <data name="menuAddNaiveServer" xml:space="preserve"> | ||||
|     <value>新增 [Naive] 設定檔</value> | ||||
|   </data> | ||||
|   <data name="menuAddShadowquicServer" xml:space="preserve"> | ||||
|     <value>新增 [Shadowquic] 設定檔</value> | ||||
|   </data> | ||||
|   <data name="TbHeaderType100" xml:space="preserve"> | ||||
|     <value>Proxy Protocol</value> | ||||
|   </data> | ||||
| </root> | ||||
|  | @ -1,4 +1,4 @@ | |||
| { | ||||
| { | ||||
| 	"log": { | ||||
| 		"level": "debug", | ||||
| 		"timestamp": true | ||||
|  | @ -14,22 +14,10 @@ | |||
| 		{ | ||||
| 			"type": "direct", | ||||
| 			"tag": "direct" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"type": "block", | ||||
| 			"tag": "block" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"tag": "dns_out", | ||||
| 			"type": "dns" | ||||
| 		} | ||||
| 	], | ||||
| 	"route": { | ||||
| 		"rules": [ | ||||
| 			{ | ||||
| 				"protocol": [ "dns" ], | ||||
| 				"outbound": "dns_out" | ||||
| 			} | ||||
| 		] | ||||
| 	} | ||||
| } | ||||
|  | @ -2,28 +2,33 @@ | |||
|   "servers": [ | ||||
|     { | ||||
|       "tag": "remote", | ||||
|       "address": "tcp://8.8.8.8", | ||||
|       "strategy": "prefer_ipv4", | ||||
|       "type": "tcp", | ||||
|       "server": "8.8.8.8", | ||||
|       "detour": "proxy" | ||||
|     }, | ||||
|     { | ||||
|       "tag": "local", | ||||
|       "address": "223.5.5.5", | ||||
|       "strategy": "prefer_ipv4", | ||||
|       "detour": "direct" | ||||
|     }, | ||||
|     { | ||||
|       "tag": "block", | ||||
|       "address": "rcode://success" | ||||
|       "type": "udp", | ||||
|       "server": "223.5.5.5" | ||||
|     } | ||||
|   ], | ||||
|   "rules": [ | ||||
|     { | ||||
|       "domain_suffix": [ | ||||
|         "googleapis.cn", | ||||
|         "gstatic.com" | ||||
|       ], | ||||
|       "server": "remote", | ||||
|       "strategy": "prefer_ipv4" | ||||
|     }, | ||||
|     { | ||||
|       "rule_set": [ | ||||
|         "geosite-cn" | ||||
|       ], | ||||
|       "server": "local" | ||||
|       "server": "local", | ||||
|       "strategy": "prefer_ipv4" | ||||
|     } | ||||
|   ], | ||||
|   "final": "remote" | ||||
|   "final": "remote", | ||||
|   "strategy": "prefer_ipv4" | ||||
| } | ||||
|  |  | |||
|  | @ -2,29 +2,33 @@ | |||
|   "servers": [ | ||||
|     { | ||||
|       "tag": "remote", | ||||
|       "address": "tcp://8.8.8.8", | ||||
|       "strategy": "prefer_ipv4", | ||||
|       "type": "tcp", | ||||
|       "server": "8.8.8.8", | ||||
|       "detour": "proxy" | ||||
|     }, | ||||
|     { | ||||
|       "tag": "local", | ||||
|       "address": "223.5.5.5", | ||||
|       "strategy": "prefer_ipv4", | ||||
|       "detour": "direct" | ||||
|     }, | ||||
|     { | ||||
|       "tag": "block", | ||||
|       "address": "rcode://success" | ||||
|       "type": "udp", | ||||
|       "server": "223.5.5.5" | ||||
|     } | ||||
|   ], | ||||
|   "rules": [ | ||||
|     { | ||||
|       "rule_set": [ | ||||
|         "geosite-cn", | ||||
|         "geosite-geolocation-cn" | ||||
|       "domain_suffix": [ | ||||
|         "googleapis.cn", | ||||
|         "gstatic.com" | ||||
|       ], | ||||
|       "server": "local" | ||||
|       "server": "remote", | ||||
|       "strategy": "prefer_ipv4" | ||||
|     }, | ||||
|     { | ||||
|       "rule_set": [ | ||||
|         "geosite-cn" | ||||
|       ], | ||||
|       "server": "local", | ||||
|       "strategy": "prefer_ipv4" | ||||
|     } | ||||
|   ], | ||||
|   "final": "remote" | ||||
|   "final": "remote", | ||||
|   "strategy": "prefer_ipv4" | ||||
| } | ||||
|  | @ -8,13 +8,13 @@ | |||
|       139, | ||||
|       5353 | ||||
|     ], | ||||
|     "outbound": "block" | ||||
|     "action": "reject" | ||||
|   }, | ||||
|   { | ||||
|     "ip_cidr": [ | ||||
|       "224.0.0.0/3", | ||||
|       "ff00::/8" | ||||
|     ], | ||||
|     "outbound": "block" | ||||
|     "action": "reject" | ||||
|   } | ||||
| ] | ||||
|  | @ -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<RetResult> GeneratePassthroughConfig(ProfileItem node) | ||||
|     { | ||||
|         return GeneratePassthroughConfig(node, AppHandler.Instance.GetLocalPort(EInboundProtocol.split)); | ||||
|     } | ||||
|     public virtual Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port) | ||||
|     { | ||||
|         return GeneratePassthroughConfig(node, port); | ||||
|     } | ||||
|     protected abstract Task<RetResult> GeneratePassthroughConfig(ProfileItem node, int port); | ||||
|     public virtual async Task<RetResult> 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<RetResult> GenerateClientConfigContent(ProfileItem node); | ||||
|     public abstract Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad); | ||||
|     public virtual Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds) | ||||
|     { | ||||
|         return GenerateClientMultipleLoadConfig(selecteds, EMultipleLoad.LeastPing); | ||||
|     } | ||||
|     public abstract Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds); | ||||
| } | ||||
|  | @ -1,22 +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<RetResult> GenerateClientConfigContent(ProfileItem node) | ||||
|     public override async Task<RetResult> GenerateClientConfigContent(ProfileItem node) | ||||
|     { | ||||
|         var ret = new RetResult(); | ||||
|         try | ||||
|  | @ -53,7 +50,18 @@ public class CoreConfigSingboxService | |||
| 
 | ||||
|             await GenInbounds(singboxConfig); | ||||
| 
 | ||||
|             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()); | ||||
|             } | ||||
| 
 | ||||
|             await GenMoreOutbounds(node, singboxConfig); | ||||
| 
 | ||||
|  | @ -78,7 +86,7 @@ public class CoreConfigSingboxService | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds) | ||||
|     public override async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds) | ||||
|     { | ||||
|         var ret = new RetResult(); | ||||
|         try | ||||
|  | @ -127,7 +135,7 @@ public class CoreConfigSingboxService | |||
| 
 | ||||
|             foreach (var it in selecteds) | ||||
|             { | ||||
|                 if (it.ConfigType == EConfigType.Custom) | ||||
|                 if (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | @ -202,16 +210,29 @@ public class CoreConfigSingboxService | |||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); | ||||
|                 await GenOutbound(item, outbound); | ||||
|                 outbound.tag = Global.ProxyTag + inbound.listen_port.ToString(); | ||||
|                 var server = await GenServer(item); | ||||
|                 if (server is null) | ||||
|                 { | ||||
|                     ret.Msg = ResUI.FailedGenDefaultConfiguration; | ||||
|                     return ret; | ||||
|                 } | ||||
|                 var tag = Global.ProxyTag + inbound.listen_port.ToString(); | ||||
|                 server.tag = tag; | ||||
|                 if (server is Endpoints4Sbox endpoint) | ||||
|                 { | ||||
|                     singboxConfig.endpoints ??= new(); | ||||
|                     singboxConfig.endpoints.Add(endpoint); | ||||
|                 } | ||||
|                 else if (server is Outbound4Sbox outbound) | ||||
|                 { | ||||
|                     singboxConfig.outbounds.Add(outbound); | ||||
|                 } | ||||
| 
 | ||||
|                 //rule | ||||
|                 Rule4Sbox rule = new() | ||||
|                 { | ||||
|                     inbound = new List<string> { inbound.tag }, | ||||
|                     outbound = outbound.tag | ||||
|                     outbound = tag | ||||
|                 }; | ||||
|                 singboxConfig.route.rules.Add(rule); | ||||
|             } | ||||
|  | @ -242,7 +263,7 @@ public class CoreConfigSingboxService | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port) | ||||
|     public override async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port) | ||||
|     { | ||||
|         var ret = new RetResult(); | ||||
|         try | ||||
|  | @ -275,7 +296,18 @@ public class CoreConfigSingboxService | |||
|             } | ||||
| 
 | ||||
|             await GenLog(singboxConfig); | ||||
|             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()); | ||||
|             } | ||||
|             await GenMoreOutbounds(node, singboxConfig); | ||||
|             await GenDnsDomains(null, singboxConfig, null); | ||||
| 
 | ||||
|  | @ -302,7 +334,7 @@ public class CoreConfigSingboxService | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds) | ||||
|     public override async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad) | ||||
|     { | ||||
|         var ret = new RetResult(); | ||||
|         try | ||||
|  | @ -339,7 +371,7 @@ public class CoreConfigSingboxService | |||
|             var proxyProfiles = new List<ProfileItem>(); | ||||
|             foreach (var it in selecteds) | ||||
|             { | ||||
|                 if (it.ConfigType == EConfigType.Custom) | ||||
|                 if (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | @ -394,7 +426,7 @@ public class CoreConfigSingboxService | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName) | ||||
|     public override async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName) | ||||
|     { | ||||
|         var ret = new RetResult(); | ||||
|         if (node == null || fileName is null) | ||||
|  | @ -475,6 +507,92 @@ public class CoreConfigSingboxService | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected override async Task<RetResult> 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<SingboxConfig>(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 | ||||
|  | @ -534,15 +652,6 @@ public class CoreConfigSingboxService | |||
|                 singboxConfig.inbounds.Add(inbound); | ||||
| 
 | ||||
|                 inbound.listen_port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); | ||||
|                 inbound.sniff = _config.Inbound.First().SniffingEnabled; | ||||
|                 inbound.sniff_override_destination = _config.Inbound.First().RouteOnly ? false : _config.Inbound.First().SniffingEnabled; | ||||
|                 inbound.domain_strategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox; | ||||
| 
 | ||||
|                 var routing = await ConfigHandler.GetDefaultRouting(_config); | ||||
|                 if (routing.DomainStrategy4Singbox.IsNotEmpty()) | ||||
|                 { | ||||
|                     inbound.domain_strategy = routing.DomainStrategy4Singbox; | ||||
|                 } | ||||
| 
 | ||||
|                 if (_config.Inbound.First().SecondLocalPortEnabled) | ||||
|                 { | ||||
|  | @ -587,8 +696,6 @@ public class CoreConfigSingboxService | |||
|                 tunInbound.mtu = _config.TunModeItem.Mtu; | ||||
|                 tunInbound.strict_route = _config.TunModeItem.StrictRoute; | ||||
|                 tunInbound.stack = _config.TunModeItem.Stack; | ||||
|                 tunInbound.sniff = _config.Inbound.First().SniffingEnabled; | ||||
|                 //tunInbound.sniff_override_destination = _config.inbound.First().routeOnly ? false : _config.inbound.First().sniffingEnabled; | ||||
|                 if (_config.TunModeItem.EnableIPv6Address == false) | ||||
|                 { | ||||
|                     tunInbound.address = ["172.18.0.1/30"]; | ||||
|  | @ -621,6 +728,17 @@ public class CoreConfigSingboxService | |||
|             outbound.server_port = node.Port; | ||||
|             outbound.type = Global.ProtocolTypes[node.ConfigType]; | ||||
| 
 | ||||
|             if (Utils.IsDomain(node.Address)) | ||||
|             { | ||||
|                 var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); | ||||
|                 var localDnsAddress = string.IsNullOrEmpty(item?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : item?.DomainDNSAddress; | ||||
|                 outbound.domain_resolver = new() | ||||
|                 { | ||||
|                     server = localDnsAddress.StartsWith("tag://") ? localDnsAddress.Substring(6) : "local_resolver", | ||||
|                     strategy = string.IsNullOrEmpty(item?.DomainStrategy4Freedom) ? null : item?.DomainStrategy4Freedom | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             switch (node.ConfigType) | ||||
|             { | ||||
|                 case EConfigType.VMess: | ||||
|  | @ -730,13 +848,9 @@ public class CoreConfigSingboxService | |||
|                         outbound.congestion_control = node.HeaderType; | ||||
|                         break; | ||||
|                     } | ||||
|                 case EConfigType.WireGuard: | ||||
|                 case EConfigType.Anytls: | ||||
|                     { | ||||
|                         outbound.private_key = node.Id; | ||||
|                         outbound.peer_public_key = node.PublicKey; | ||||
|                         outbound.reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(); | ||||
|                         outbound.local_address = Utils.String2List(node.RequestHost); | ||||
|                         outbound.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(); | ||||
|                         outbound.password = node.Id; | ||||
|                         break; | ||||
|                     } | ||||
|             } | ||||
|  | @ -752,6 +866,76 @@ public class CoreConfigSingboxService | |||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     private async Task<int> GenEndpoint(ProfileItem node, Endpoints4Sbox endpoint) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             endpoint.address = Utils.String2List(node.RequestHost); | ||||
|             endpoint.type = Global.ProtocolTypes[node.ConfigType]; | ||||
| 
 | ||||
|             if (Utils.IsDomain(node.Address)) | ||||
|             { | ||||
|                 var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); | ||||
|                 var localDnsAddress = string.IsNullOrEmpty(item?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : item?.DomainDNSAddress; | ||||
|                 endpoint.domain_resolver = new() | ||||
|                 { | ||||
|                     server = localDnsAddress.StartsWith("tag://") ? localDnsAddress.Substring(6) : "local_resolver", | ||||
|                     strategy = string.IsNullOrEmpty(item?.DomainStrategy4Freedom) ? null : item?.DomainStrategy4Freedom | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             switch (node.ConfigType) | ||||
|             { | ||||
|                 case EConfigType.WireGuard: | ||||
|                     { | ||||
|                         var peer = new Peer4Sbox | ||||
|                         { | ||||
|                             public_key = node.PublicKey, | ||||
|                             reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(), | ||||
|                             address = node.Address, | ||||
|                             port = node.Port, | ||||
|                             // TODO default ["0.0.0.0/0", "::/0"] | ||||
|                             allowed_ips = new() { "0.0.0.0/0", "::/0" }, | ||||
|                         }; | ||||
|                         endpoint.private_key = node.Id; | ||||
|                         endpoint.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(); | ||||
|                         endpoint.peers = new() { peer }; | ||||
|                         break; | ||||
|                     } | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             Logging.SaveLog(_tag, ex); | ||||
|         } | ||||
|         return await Task.FromResult(0); | ||||
|     } | ||||
| 
 | ||||
|     private async Task<BaseServer4Sbox?> GenServer(ProfileItem node) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); | ||||
|             if (node.ConfigType == EConfigType.WireGuard) | ||||
|             { | ||||
|                 var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound); | ||||
|                 await GenEndpoint(node, endpoint); | ||||
|                 return endpoint; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); | ||||
|                 await GenOutbound(node, outbound); | ||||
|                 return outbound; | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             Logging.SaveLog(_tag, ex); | ||||
|         } | ||||
|         return await Task.FromResult<BaseServer4Sbox?>(null); | ||||
|     } | ||||
| 
 | ||||
|     private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound) | ||||
|     { | ||||
|         try | ||||
|  | @ -918,26 +1102,42 @@ public class CoreConfigSingboxService | |||
|             } | ||||
| 
 | ||||
|             //current proxy | ||||
|             var outbound = singboxConfig.outbounds.First(); | ||||
|             BaseServer4Sbox? outbound = singboxConfig.endpoints?.FirstOrDefault(t => t.tag == Global.ProxyTag) == null ? singboxConfig.outbounds.First() : null; | ||||
| 
 | ||||
|             var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); | ||||
| 
 | ||||
|             //Previous proxy | ||||
|             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)) | ||||
|             { | ||||
|                 var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); | ||||
|                 await GenOutbound(prevNode, prevOutbound); | ||||
|                 prevOutboundTag = $"prev-{Global.ProxyTag}"; | ||||
|                 prevOutbound.tag = prevOutboundTag; | ||||
|                 singboxConfig.outbounds.Add(prevOutbound); | ||||
|             } | ||||
|             var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag); | ||||
| 
 | ||||
|             if (nextOutbound is not null) | ||||
|                 var prevServer = await GenServer(prevNode); | ||||
|                 prevServer.tag = prevOutboundTag; | ||||
|                 if (prevServer is Endpoints4Sbox endpoint) | ||||
|                 { | ||||
|                 singboxConfig.outbounds.Insert(0, nextOutbound); | ||||
|                     singboxConfig.endpoints ??= new(); | ||||
|                     singboxConfig.endpoints.Add(endpoint); | ||||
|                 } | ||||
|                 else if (prevServer is Outbound4Sbox outboundPrev) | ||||
|                 { | ||||
|                     singboxConfig.outbounds.Add(outboundPrev); | ||||
|                 } | ||||
|             } | ||||
|             var nextServer = await GenChainOutbounds(subItem, outbound, prevOutboundTag); | ||||
| 
 | ||||
|             if (nextServer is not null) | ||||
|             { | ||||
|                 if (nextServer is Endpoints4Sbox endpoint) | ||||
|                 { | ||||
|                     singboxConfig.endpoints ??= new(); | ||||
|                     singboxConfig.endpoints.Insert(0, endpoint); | ||||
|                 } | ||||
|                 else if (nextServer is Outbound4Sbox outboundNext) | ||||
|                 { | ||||
|                     singboxConfig.outbounds.Insert(0, outboundNext); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|  | @ -960,11 +1160,13 @@ public class CoreConfigSingboxService | |||
|             } | ||||
| 
 | ||||
|             var resultOutbounds = new List<Outbound4Sbox>(); | ||||
|             var resultEndpoints = new List<Endpoints4Sbox>(); // For endpoints | ||||
|             var prevOutbounds = new List<Outbound4Sbox>(); // Separate list for prev outbounds | ||||
|             var prevEndpoints = new List<Endpoints4Sbox>(); // Separate list for prev endpoints | ||||
|             var proxyTags = new List<string>(); // For selector and urltest outbounds | ||||
| 
 | ||||
|             // Cache for chain proxies to avoid duplicate generation | ||||
|             var nextProxyCache = new Dictionary<string, Outbound4Sbox?>(); | ||||
|             var nextProxyCache = new Dictionary<string, BaseServer4Sbox?>(); | ||||
|             var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag | ||||
|             int prevIndex = 0; // Index for prev outbounds | ||||
| 
 | ||||
|  | @ -976,19 +1178,18 @@ public class CoreConfigSingboxService | |||
| 
 | ||||
|                 // Handle proxy chain | ||||
|                 string? prevTag = null; | ||||
|                 var currentOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); | ||||
|                 var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null); | ||||
|                 if (nextOutbound != null) | ||||
|                 var currentServer = await GenServer(node); | ||||
|                 var nextServer = nextProxyCache.GetValueOrDefault(node.Subid, null); | ||||
|                 if (nextServer != null) | ||||
|                 { | ||||
|                     nextOutbound = JsonUtils.DeepCopy(nextOutbound); | ||||
|                     nextServer = JsonUtils.DeepCopy(nextServer); | ||||
|                 } | ||||
| 
 | ||||
|                 var subItem = await AppHandler.Instance.GetSubItem(node.Subid); | ||||
| 
 | ||||
|                 // current proxy | ||||
|                 await GenOutbound(node, currentOutbound); | ||||
|                 currentOutbound.tag = $"{Global.ProxyTag}-{index}"; | ||||
|                 proxyTags.Add(currentOutbound.tag); | ||||
|                 currentServer.tag = $"{Global.ProxyTag}-{index}"; | ||||
|                 proxyTags.Add(currentServer.tag); | ||||
| 
 | ||||
|                 if (!node.Subid.IsNullOrEmpty()) | ||||
|                 { | ||||
|  | @ -1000,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<Outbound4Sbox>(txtOutbound); | ||||
|                             await GenOutbound(prevNode, prevOutbound); | ||||
|  | @ -1011,19 +1212,33 @@ public class CoreConfigSingboxService | |||
|                         prevProxyTags[node.Subid] = prevTag; | ||||
|                     } | ||||
| 
 | ||||
|                     nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound); | ||||
|                     nextServer = await GenChainOutbounds(subItem, currentServer, prevTag, nextServer); | ||||
|                     if (!nextProxyCache.ContainsKey(node.Subid)) | ||||
|                     { | ||||
|                         nextProxyCache[node.Subid] = nextOutbound; | ||||
|                         nextProxyCache[node.Subid] = nextServer; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (nextOutbound is not null) | ||||
|                 if (nextServer is not null) | ||||
|                 { | ||||
|                     if (nextServer is Endpoints4Sbox nextEndpoint) | ||||
|                     { | ||||
|                         resultEndpoints.Add(nextEndpoint); | ||||
|                     } | ||||
|                     else if (nextServer is Outbound4Sbox nextOutbound) | ||||
|                     { | ||||
|                         resultOutbounds.Add(nextOutbound); | ||||
|                     } | ||||
|                 } | ||||
|                 if (currentServer is Endpoints4Sbox currentEndpoint) | ||||
|                 { | ||||
|                     resultEndpoints.Add(currentEndpoint); | ||||
|                 } | ||||
|                 else if (currentServer is Outbound4Sbox currentOutbound) | ||||
|                 { | ||||
|                     resultOutbounds.Add(currentOutbound); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // Add urltest outbound (auto selection based on latency) | ||||
|             if (proxyTags.Count > 0) | ||||
|  | @ -1055,6 +1270,9 @@ public class CoreConfigSingboxService | |||
|             resultOutbounds.AddRange(prevOutbounds); | ||||
|             resultOutbounds.AddRange(singboxConfig.outbounds); | ||||
|             singboxConfig.outbounds = resultOutbounds; | ||||
|             singboxConfig.endpoints ??= new List<Endpoints4Sbox>(); | ||||
|             resultEndpoints.AddRange(singboxConfig.endpoints); | ||||
|             singboxConfig.endpoints = resultEndpoints; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|  | @ -1076,7 +1294,7 @@ public class CoreConfigSingboxService | |||
|     /// <returns> | ||||
|     /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. | ||||
|     /// </returns> | ||||
|     private async Task<Outbound4Sbox?> GenChainOutbounds(SubItem subItem, Outbound4Sbox outbound, string? prevOutboundTag, Outbound4Sbox? nextOutbound = null) | ||||
|     private async Task<BaseServer4Sbox?> GenChainOutbounds(SubItem subItem, BaseServer4Sbox outbound, string? prevOutboundTag, BaseServer4Sbox? nextOutbound = null) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|  | @ -1090,13 +1308,9 @@ 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)) | ||||
|             { | ||||
|                 if (nextOutbound == null) | ||||
|                 { | ||||
|                     nextOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); | ||||
|                     await GenOutbound(nextNode, nextOutbound); | ||||
|                 } | ||||
|                 nextOutbound ??= await GenServer(nextNode); | ||||
|                 nextOutbound.tag = outbound.tag; | ||||
| 
 | ||||
|                 outbound.tag = $"mid-{outbound.tag}"; | ||||
|  | @ -1115,7 +1329,7 @@ public class CoreConfigSingboxService | |||
|     { | ||||
|         try | ||||
|         { | ||||
|             var dnsOutbound = "dns_out"; | ||||
|             singboxConfig.route.final = Global.ProxyTag; | ||||
| 
 | ||||
|             if (_config.TunModeItem.EnableTun) | ||||
|             { | ||||
|  | @ -1131,7 +1345,7 @@ public class CoreConfigSingboxService | |||
|                 singboxConfig.route.rules.Add(new() | ||||
|                 { | ||||
|                     port = new() { 53 }, | ||||
|                     outbound = dnsOutbound, | ||||
|                     action = "hijack-dns", | ||||
|                     process_name = lstDnsExe | ||||
|                 }); | ||||
| 
 | ||||
|  | @ -1142,13 +1356,25 @@ public class CoreConfigSingboxService | |||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             if (!_config.Inbound.First().SniffingEnabled) | ||||
|             if (_config.Inbound.First().SniffingEnabled) | ||||
|             { | ||||
|                 singboxConfig.route.rules.Add(new() | ||||
|                 { | ||||
|                     port = [53], | ||||
|                     network = ["udp"], | ||||
|                     outbound = dnsOutbound | ||||
|                     action = "sniff" | ||||
|                 }); | ||||
|                 singboxConfig.route.rules.Add(new() | ||||
|                 { | ||||
|                     protocol = new() { "dns" }, | ||||
|                     action = "hijack-dns" | ||||
|                 }); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 singboxConfig.route.rules.Add(new() | ||||
|                 { | ||||
|                     port = new() { 53 }, | ||||
|                     network = new() { "udp" }, | ||||
|                     action = "hijack-dns" | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|  | @ -1163,7 +1389,24 @@ public class CoreConfigSingboxService | |||
|                 clash_mode = ERuleMode.Global.ToString() | ||||
|             }); | ||||
| 
 | ||||
|             var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox; | ||||
|             var defaultRouting = await ConfigHandler.GetDefaultRouting(_config); | ||||
|             if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty()) | ||||
|             { | ||||
|                 domainStrategy = defaultRouting.DomainStrategy4Singbox; | ||||
|             } | ||||
|             var resolveRule = new Rule4Sbox | ||||
|             { | ||||
|                 action = "resolve", | ||||
|                 strategy = domainStrategy | ||||
|             }; | ||||
|             if (_config.RoutingBasicItem.DomainStrategy == "IPOnDemand") | ||||
|             { | ||||
|                 singboxConfig.route.rules.Add(resolveRule); | ||||
|             } | ||||
| 
 | ||||
|             var routing = await ConfigHandler.GetDefaultRouting(_config); | ||||
|             var ipRules = new List<RulesItem>(); | ||||
|             if (routing != null) | ||||
|             { | ||||
|                 var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet); | ||||
|  | @ -1172,10 +1415,22 @@ public class CoreConfigSingboxService | |||
|                     if (item.Enabled) | ||||
|                     { | ||||
|                         await GenRoutingUserRule(item, singboxConfig); | ||||
|                         if (item.Ip != null && item.Ip.Count > 0) | ||||
|                         { | ||||
|                             ipRules.Add(item); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if (_config.RoutingBasicItem.DomainStrategy == "IPIfNonMatch") | ||||
|             { | ||||
|                 singboxConfig.route.rules.Add(resolveRule); | ||||
|                 foreach (var item in ipRules) | ||||
|                 { | ||||
|                     await GenRoutingUserRule(item, singboxConfig); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             Logging.SaveLog(_tag, ex); | ||||
|  | @ -1222,10 +1477,15 @@ public class CoreConfigSingboxService | |||
|             item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig); | ||||
|             var rules = singboxConfig.route.rules; | ||||
| 
 | ||||
|             var rule = new Rule4Sbox() | ||||
|             var rule = new Rule4Sbox(); | ||||
|             if (item.OutboundTag == "block") | ||||
|             { | ||||
|                 outbound = item.OutboundTag, | ||||
|             }; | ||||
|                 rule.action = "reject"; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 rule.outbound = item.OutboundTag; | ||||
|             } | ||||
| 
 | ||||
|             if (item.Port.IsNotEmpty()) | ||||
|             { | ||||
|  | @ -1349,24 +1609,28 @@ public class CoreConfigSingboxService | |||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         else if (address.StartsWith("geoip:!")) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|         else if (address.Equals("geoip:private")) | ||||
|         { | ||||
|             rule.ip_is_private = true; | ||||
|         } | ||||
|         else if (address.StartsWith("geoip:")) | ||||
|         { | ||||
|             if (rule.geoip is null) | ||||
|             { rule.geoip = new(); } | ||||
|             rule.geoip ??= new(); | ||||
|             rule.geoip?.Add(address.Substring(6)); | ||||
|         } | ||||
|         else if (address.Equals("geoip:!private")) | ||||
|         { | ||||
|             rule.ip_is_private = false; | ||||
|         } | ||||
|         else if (address.StartsWith("geoip:!")) | ||||
|         { | ||||
|             rule.geoip ??= new(); | ||||
|             rule.geoip?.Add(address.Substring(6)); | ||||
|             rule.invert = true; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if (rule.ip_cidr is null) | ||||
|             { rule.ip_cidr = new(); } | ||||
|             rule.ip_cidr ??= new(); | ||||
|             rule.ip_cidr?.Add(address); | ||||
|         } | ||||
|         return true; | ||||
|  | @ -1381,18 +1645,29 @@ 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; | ||||
|         } | ||||
| 
 | ||||
|         var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); | ||||
|         var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); | ||||
|         await GenOutbound(node, outbound); | ||||
|         outbound.tag = Global.ProxyTag + node.IndexId.ToString(); | ||||
|         singboxConfig.outbounds.Add(outbound); | ||||
|         var server = await GenServer(node); | ||||
|         if (server is null) | ||||
|         { | ||||
|             return Global.ProxyTag; | ||||
|         } | ||||
| 
 | ||||
|         return outbound.tag; | ||||
|         server.tag = Global.ProxyTag + node.IndexId.ToString(); | ||||
|         if (server is Endpoints4Sbox endpoint) | ||||
|         { | ||||
|             singboxConfig.endpoints ??= new(); | ||||
|             singboxConfig.endpoints.Add(endpoint); | ||||
|         } | ||||
|         else if (server is Outbound4Sbox outbound) | ||||
|         { | ||||
|             singboxConfig.outbounds.Add(outbound); | ||||
|         } | ||||
| 
 | ||||
|         return server.tag; | ||||
|     } | ||||
| 
 | ||||
|     private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig) | ||||
|  | @ -1417,8 +1692,15 @@ public class CoreConfigSingboxService | |||
|             } | ||||
|             singboxConfig.dns = dns4Sbox; | ||||
| 
 | ||||
|             if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty()) | ||||
|             { | ||||
|                 await GenDnsDomains(node, singboxConfig, item); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 await GenDnsDomainsLegacy(node, singboxConfig, item); | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             Logging.SaveLog(_tag, ex); | ||||
|  | @ -1432,6 +1714,75 @@ public class CoreConfigSingboxService | |||
|         dns4Sbox.servers ??= []; | ||||
|         dns4Sbox.rules ??= []; | ||||
| 
 | ||||
|         var tag = "local_resolver"; | ||||
|         var localDnsAddress = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress; | ||||
| 
 | ||||
|         if (localDnsAddress.StartsWith("tag://")) | ||||
|         { | ||||
|             tag = localDnsAddress.Substring(6); | ||||
| 
 | ||||
|             var localDnsTag = "local_local"; | ||||
| 
 | ||||
|             dns4Sbox.servers.Add(new() | ||||
|             { | ||||
|                 tag = localDnsTag, | ||||
|                 type = "local" | ||||
|             }); | ||||
| 
 | ||||
|             dns4Sbox.rules.Insert(0, new() | ||||
|             { | ||||
|                 server = localDnsTag, | ||||
|                 clash_mode = ERuleMode.Direct.ToString() | ||||
|             }); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             var (dnsType, dnsHost, dnsPort, dnsPath) = ParseDnsAddress(localDnsAddress); | ||||
| 
 | ||||
|             dns4Sbox.servers.Add(new() | ||||
|             { | ||||
|                 tag = tag, | ||||
|                 type = dnsType, | ||||
|                 server = dnsHost, | ||||
|                 Interface = dnsType == "dhcp" ? dnsHost : null, | ||||
|                 server_port = dnsPort, | ||||
|                 path = dnsPath | ||||
|             }); | ||||
| 
 | ||||
|             dns4Sbox.rules.Insert(0, new() | ||||
|             { | ||||
|                 server = tag, | ||||
|                 clash_mode = ERuleMode.Direct.ToString() | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         dns4Sbox.rules.Insert(0, new() | ||||
|         { | ||||
|             server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote", | ||||
|             clash_mode = ERuleMode.Global.ToString() | ||||
|         }); | ||||
| 
 | ||||
|         //Tun2SocksAddress | ||||
|         if (_config.TunModeItem.EnableTun && node?.ConfigType == EConfigType.SOCKS && Utils.IsDomain(node?.Sni)) | ||||
|         { | ||||
|             dns4Sbox.rules.Insert(0, new() | ||||
|             { | ||||
|                 server = tag, | ||||
|                 domain = [node?.Sni], | ||||
|                 strategy = string.IsNullOrEmpty(dNSItem?.DomainStrategy4Freedom) ? null : dNSItem?.DomainStrategy4Freedom | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         singboxConfig.dns = dns4Sbox; | ||||
|         return await Task.FromResult(0); | ||||
|     } | ||||
| 
 | ||||
|     private async Task<int> GenDnsDomainsLegacy(ProfileItem? node, SingboxConfig singboxConfig, DNSItem? dNSItem) | ||||
|     { | ||||
|         var dns4Sbox = singboxConfig.dns ?? new(); | ||||
|         dns4Sbox.servers ??= []; | ||||
|         dns4Sbox.rules ??= []; | ||||
| 
 | ||||
|         var tag = "local_local"; | ||||
|         dns4Sbox.servers.Add(new() | ||||
|         { | ||||
|  | @ -1479,6 +1830,91 @@ public class CoreConfigSingboxService | |||
|         return await Task.FromResult(0); | ||||
|     } | ||||
| 
 | ||||
|     private (string type, string? host, int? port, string? path) ParseDnsAddress(string address) | ||||
|     { | ||||
|         string type = "udp"; | ||||
|         string? host = null; | ||||
|         int? port = null; | ||||
|         string? path = null; | ||||
| 
 | ||||
|         if (address is "local" or "localhost") | ||||
|         { | ||||
|             return ("local", null, null, null); | ||||
|         } | ||||
| 
 | ||||
|         if (address.StartsWith("dhcp://", StringComparison.OrdinalIgnoreCase)) | ||||
|         { | ||||
|             string interface_name = address.Substring(7); | ||||
|             return ("dhcp", interface_name == "auto" ? null : interface_name, null, null); | ||||
|         } | ||||
| 
 | ||||
|         if (!address.Contains("://")) | ||||
|         { | ||||
|             // udp dns | ||||
|             host = address; | ||||
|             return (type, host, port, path); | ||||
|         } | ||||
| 
 | ||||
|         try | ||||
|         { | ||||
|             int protocolEndIndex = address.IndexOf("://", StringComparison.Ordinal); | ||||
|             type = address.Substring(0, protocolEndIndex).ToLower(); | ||||
| 
 | ||||
|             var uri = new Uri(address); | ||||
|             host = uri.Host; | ||||
| 
 | ||||
|             if (!uri.IsDefaultPort) | ||||
|             { | ||||
|                 port = uri.Port; | ||||
|             } | ||||
| 
 | ||||
|             if ((type == "https" || type == "h3") && !string.IsNullOrEmpty(uri.AbsolutePath) && uri.AbsolutePath != "/") | ||||
|             { | ||||
|                 path = uri.AbsolutePath; | ||||
|             } | ||||
|         } | ||||
|         catch (UriFormatException) | ||||
|         { | ||||
|             int protocolEndIndex = address.IndexOf("://", StringComparison.Ordinal); | ||||
|             if (protocolEndIndex > 0) | ||||
|             { | ||||
|                 type = address.Substring(0, protocolEndIndex).ToLower(); | ||||
|                 string remaining = address.Substring(protocolEndIndex + 3); | ||||
| 
 | ||||
|                 int portIndex = remaining.IndexOf(':'); | ||||
|                 int pathIndex = remaining.IndexOf('/'); | ||||
| 
 | ||||
|                 if (portIndex > 0) | ||||
|                 { | ||||
|                     host = remaining.Substring(0, portIndex); | ||||
|                     string portPart = pathIndex > portIndex | ||||
|                         ? remaining.Substring(portIndex + 1, pathIndex - portIndex - 1) | ||||
|                         : remaining.Substring(portIndex + 1); | ||||
| 
 | ||||
|                     if (int.TryParse(portPart, out int parsedPort)) | ||||
|                     { | ||||
|                         port = parsedPort; | ||||
|                     } | ||||
|                 } | ||||
|                 else if (pathIndex > 0) | ||||
|                 { | ||||
|                     host = remaining.Substring(0, pathIndex); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     host = remaining; | ||||
|                 } | ||||
| 
 | ||||
|                 if (pathIndex > 0 && (type == "https" || type == "h3")) | ||||
|                 { | ||||
|                     path = remaining.Substring(pathIndex); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return (type, host, port, path); | ||||
|     } | ||||
| 
 | ||||
|     private async Task<int> GenExperimental(SingboxConfig singboxConfig) | ||||
|     { | ||||
|         //if (_config.guiItem.enableStatistics) | ||||
|  |  | |||
|  | @ -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<RetResult> GenerateClientConfigContent(ProfileItem node) | ||||
|     public override async Task<RetResult> GenerateClientConfigContent(ProfileItem node) | ||||
|     { | ||||
|         var ret = new RetResult(); | ||||
|         try | ||||
|  | @ -77,7 +70,7 @@ public class CoreConfigV2rayService | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad) | ||||
|     public override async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad) | ||||
|     { | ||||
|         var ret = new RetResult(); | ||||
| 
 | ||||
|  | @ -116,11 +109,7 @@ public class CoreConfigV2rayService | |||
|             var proxyProfiles = new List<ProfileItem>(); | ||||
|             foreach (var it in selecteds) | ||||
|             { | ||||
|                 if (it.ConfigType == EConfigType.Custom) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (it.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC) | ||||
|                 if (!Global.XraySupportConfigType.Contains(it.ConfigType)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | @ -206,7 +195,7 @@ public class CoreConfigV2rayService | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds) | ||||
|     public override async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> 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<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port) | ||||
|     public override async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port) | ||||
|     { | ||||
|         var ret = new RetResult(); | ||||
|         try | ||||
|  | @ -416,6 +405,80 @@ public class CoreConfigV2rayService | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected override async Task<RetResult> 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<V2rayConfig>(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,9 +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) | ||||
|             || !Global.SingboxSupportConfigType.Contains(node.ConfigType)) | ||||
|         { | ||||
|             return Global.ProxyTag; | ||||
|         } | ||||
|  | @ -1219,9 +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 | ||||
|                     && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType) | ||||
|                     && Utils.IsDomain(prevNode.Address)) | ||||
|                 { | ||||
|                     domainList.Add(prevNode.Address); | ||||
|  | @ -1230,9 +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 | ||||
|                     && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType) | ||||
|                     && Utils.IsDomain(nextNode.Address)) | ||||
|                 { | ||||
|                     domainList.Add(nextNode.Address); | ||||
|  | @ -1348,9 +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) | ||||
|                 && Global.XraySupportConfigType.Contains(prevNode.ConfigType)) | ||||
|             { | ||||
|                 var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound); | ||||
|                 await GenOutbound(prevNode, prevOutbound); | ||||
|  | @ -1423,9 +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) | ||||
|                             && !Global.XraySupportConfigType.Contains(prevNode.ConfigType)) | ||||
|                         { | ||||
|                             var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound); | ||||
|                             await GenOutbound(prevNode, prevOutbound); | ||||
|  | @ -1492,9 +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) | ||||
|                 && !Global.XraySupportConfigType.Contains(nextNode.ConfigType)) | ||||
|             { | ||||
|                 if (nextOutbound == null) | ||||
|                 { | ||||
|  |  | |||
|  | @ -0,0 +1,43 @@ | |||
| namespace ServiceLib.Services.CoreConfig.Minimal; | ||||
| public class CoreConfigBrookService(Config config) : CoreConfigServiceMinimalBase(config) | ||||
| { | ||||
|     protected override async Task<RetResult> 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,19 +1,21 @@ | |||
| namespace ServiceLib.Services.CoreConfig; | ||||
| namespace ServiceLib.Services.CoreConfig.Minimal; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// Core configuration file processing class | ||||
| /// </summary> | ||||
| 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<RetResult> GeneratePassthroughConfig(ProfileItem node, int port) | ||||
|     { | ||||
|         _config = config; | ||||
|         var ret = new RetResult | ||||
|         { | ||||
|             Success = false, | ||||
|             Msg = ResUI.OperationFailed | ||||
|         }; | ||||
|         return await Task.FromResult(ret); | ||||
|     } | ||||
| 
 | ||||
|     public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName) | ||||
|     public override async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName) | ||||
|     { | ||||
|         var ret = new RetResult(); | ||||
|         if (node == null || fileName is null) | ||||
|  | @ -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<RetResult> 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<RetResult> 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<JsonObject>(fileContent); | ||||
|             if (jsonContent != null) | ||||
|             { | ||||
|                 File.Copy(addressFileName, fileName); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // If it's YAML, convert to JSON and write it | ||||
|                 var yamlContent = YamlUtils.FromYaml<Dictionary<string, object>>(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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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<RetResult> 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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<RetResult> 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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<RetResult> 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<string, object>(); | ||||
| 
 | ||||
|             // 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<string, object> | ||||
|             { | ||||
|                 ["type"] = "socks5", | ||||
|                 ["listen"] = Global.Loopback + ":" + port.ToString() | ||||
|             }; | ||||
|             configYamlNode["inbound"] = inboundNode; | ||||
| 
 | ||||
|             // outbound | ||||
|             var alpn = new List<string>(); | ||||
|             foreach (var item in node.GetAlpn() ?? new List<string>()) | ||||
|             { | ||||
|                 alpn.Add(item); | ||||
|             } | ||||
|             if (alpn.Count == 0) | ||||
|             { | ||||
|                 alpn.Add("h3"); | ||||
|             } | ||||
| 
 | ||||
|             var outboundNode = new Dictionary<string, object> | ||||
|             { | ||||
|                 ["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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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<RetResult> 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<string>()) | ||||
|             { | ||||
|                 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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<ServerTestItem>(); | ||||
|         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<Task> 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<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize) | ||||
|     { | ||||
|         List<List<ServerTestItem>> lstTest = new(); | ||||
|         var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC)).ToList(); | ||||
|         var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC).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++) | ||||
|         { | ||||
|  |  | |||
|  | @ -20,10 +20,15 @@ public class MainWindowViewModel : MyReactiveObject | |||
|     public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; } | ||||
|     public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; } | ||||
|     public ReactiveCommand<Unit, Unit> AddWireguardServerCmd { get; } | ||||
|     public ReactiveCommand<Unit, Unit> AddAnytlsServerCmd { get; } | ||||
|     public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; } | ||||
|     public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; } | ||||
|     public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; } | ||||
|     public ReactiveCommand<Unit, Unit> AddServerViaImageCmd { get; } | ||||
|     public ReactiveCommand<Unit, Unit> AddBrookServerCmd { get; } | ||||
|     public ReactiveCommand<Unit, Unit> AddJuicityServerCmd { get; } | ||||
|     public ReactiveCommand<Unit, Unit> AddNaiveServerCmd { get; } | ||||
|     public ReactiveCommand<Unit, Unit> AddShadowquicServerCmd { get; } | ||||
| 
 | ||||
|     //Subscription | ||||
|     public ReactiveCommand<Unit, Unit> SubSettingCmd { get; } | ||||
|  | @ -111,6 +116,26 @@ public class MainWindowViewModel : MyReactiveObject | |||
|         { | ||||
|             await AddServerAsync(true, EConfigType.WireGuard); | ||||
|         }); | ||||
|         AddAnytlsServerCmd = ReactiveCommand.CreateFromTask(async () => | ||||
|         { | ||||
|             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); | ||||
|  |  | |||
|  | @ -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<Unit, Unit> SaveCmd { get; } | ||||
| 
 | ||||
|     public OptionSettingViewModel(Func<EViewAction, object?, Task<bool>>? 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<CoreTypeItem>(); | ||||
|         } | ||||
|         _config.CoreTypeItem ??= new List<CoreTypeItem>(); | ||||
| 
 | ||||
|         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<CoreTypeItem>(), | ||||
|             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; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -533,6 +533,171 @@ | |||
|                         HorizontalAlignment="Left" | ||||
|                         Watermark="1500" /> | ||||
|                 </Grid> | ||||
|                 <Grid | ||||
|                     x:Name="gridAnytls" | ||||
|                     Grid.Row="2" | ||||
|                     ColumnDefinitions="180,Auto" | ||||
|                     IsVisible="False" | ||||
|                     RowDefinitions="Auto,Auto,Auto"> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Text="{x:Static resx:ResUI.TbId3}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtId10" | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" /> | ||||
|                 </Grid> | ||||
|                 <Grid | ||||
|                     x:Name="gridNaive" | ||||
|                     Grid.Row="2" | ||||
|                     ColumnDefinitions="180,Auto" | ||||
|                     IsVisible="False" | ||||
|                     RowDefinitions="Auto,Auto,Auto"> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Text="{x:Static resx:ResUI.TbId3}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtId100" | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="2" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Text="{x:Static resx:ResUI.TbHeaderType100}" /> | ||||
|                     <ComboBox | ||||
|                         x:Name="cmbHeaderType100" | ||||
|                         Grid.Row="2" | ||||
|                         Grid.Column="1" | ||||
|                         Width="200" | ||||
|                         Margin="{StaticResource Margin4}" /> | ||||
|                 </Grid> | ||||
|                 <Grid | ||||
|                     x:Name="gridJuicity" | ||||
|                     Grid.Row="2" | ||||
|                     ColumnDefinitions="180,Auto" | ||||
|                     IsVisible="False" | ||||
|                     RowDefinitions="Auto,Auto,Auto,Auto"> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Text="{x:Static resx:ResUI.TbId}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtId101" | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="2" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Text="{x:Static resx:ResUI.TbId3}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtSecurity101" | ||||
|                         Grid.Row="2" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="3" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Text="{x:Static resx:ResUI.TbHeaderType8}" /> | ||||
|                     <ComboBox | ||||
|                         x:Name="cmbHeaderType101" | ||||
|                         Grid.Row="3" | ||||
|                         Grid.Column="1" | ||||
|                         Width="200" | ||||
|                         Margin="{StaticResource Margin4}" /> | ||||
|                 </Grid> | ||||
|                 <Grid | ||||
|                     x:Name="gridBrook" | ||||
|                     Grid.Row="2" | ||||
|                     ColumnDefinitions="180,Auto" | ||||
|                     IsVisible="False" | ||||
|                     RowDefinitions="Auto,Auto,Auto"> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Text="{x:Static resx:ResUI.TbId3}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtId102" | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" /> | ||||
|                 </Grid> | ||||
|                 <Grid | ||||
|                     x:Name="gridShadowquic" | ||||
|                     Grid.Row="2" | ||||
|                     ColumnDefinitions="180,Auto" | ||||
|                     IsVisible="False" | ||||
|                     RowDefinitions="Auto,Auto,Auto,Auto"> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Text="{x:Static resx:ResUI.TbId}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtId103" | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="2" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Text="{x:Static resx:ResUI.TbId3}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtSecurity103" | ||||
|                         Grid.Row="2" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="3" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Text="{x:Static resx:ResUI.TbHeaderType8}" /> | ||||
|                     <ComboBox | ||||
|                         x:Name="cmbHeaderType103" | ||||
|                         Grid.Row="3" | ||||
|                         Grid.Column="1" | ||||
|                         Width="200" | ||||
|                         Margin="{StaticResource Margin4}" /> | ||||
|                 </Grid> | ||||
| 
 | ||||
|                 <Separator | ||||
|                     x:Name="sepa2" | ||||
|  |  | |||
|  | @ -78,7 +78,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel> | |||
|                 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<AddServerViewModel> | |||
|                 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: | ||||
|  | @ -102,6 +102,55 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel> | |||
|                 gridTls.IsVisible = false; | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case EConfigType.Anytls: | ||||
|                 gridAnytls.IsVisible = true; | ||||
|                 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; | ||||
| 
 | ||||
|  | @ -167,6 +216,31 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel> | |||
|                     this.Bind(ViewModel, vm => vm.SelectedSource.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables); | ||||
|                     this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables); | ||||
|                     break; | ||||
| 
 | ||||
|                 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); | ||||
|  |  | |||
|  | @ -46,6 +46,12 @@ | |||
|                         <Separator /> | ||||
|                         <MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" /> | ||||
|                         <MenuItem x:Name="menuAddTuicServer" Header="{x:Static resx:ResUI.menuAddTuicServer}" /> | ||||
|                         <MenuItem x:Name="menuAddAnytlsServer" Header="{x:Static resx:ResUI.menuAddAnytlsServer}" /> | ||||
|                         <Separator /> | ||||
|                         <MenuItem x:Name="menuAddBrookServer" Header="{x:Static resx:ResUI.menuAddBrookServer}" /> | ||||
|                         <MenuItem x:Name="menuAddJuicityServer" Header="{x:Static resx:ResUI.menuAddJuicityServer}" /> | ||||
|                         <MenuItem x:Name="menuAddNaiveServer" Header="{x:Static resx:ResUI.menuAddNaiveServer}" /> | ||||
|                         <MenuItem x:Name="menuAddShadowquicServer" Header="{x:Static resx:ResUI.menuAddShadowquicServer}" /> | ||||
|                     </MenuItem> | ||||
| 
 | ||||
|                     <MenuItem Padding="8,0"> | ||||
|  |  | |||
|  | @ -83,6 +83,11 @@ public partial class MainWindow : WindowBase<MainWindowViewModel> | |||
|             this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables); | ||||
|             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); | ||||
|  |  | |||
|  | @ -802,10 +802,35 @@ | |||
|             </TabItem> | ||||
| 
 | ||||
|             <TabItem Header="{x:Static resx:ResUI.TbSettingsCoreType}"> | ||||
|                 <ScrollViewer VerticalScrollBarVisibility="Visible"> | ||||
|                     <Grid Margin="{StaticResource Margin4}"> | ||||
|                         <Grid.RowDefinitions> | ||||
|                             <RowDefinition Height="Auto" /> | ||||
|                         </Grid.RowDefinitions> | ||||
|                         <Grid.ColumnDefinitions> | ||||
|                             <ColumnDefinition Width="Auto" /> | ||||
|                             <ColumnDefinition Width="Auto" /> | ||||
|                         </Grid.ColumnDefinitions> | ||||
| 
 | ||||
|                         <Grid | ||||
|                     Margin="{StaticResource Margin4}" | ||||
|                     ColumnDefinitions="Auto,Auto" | ||||
|                     RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> | ||||
|                             Grid.Row="0" | ||||
|                             Grid.Column="0" | ||||
|                             Margin="{StaticResource Margin4}"> | ||||
|                             <Grid.RowDefinitions> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                             </Grid.RowDefinitions> | ||||
|                             <Grid.ColumnDefinitions> | ||||
|                                 <ColumnDefinition Width="Auto" /> | ||||
|                                 <ColumnDefinition Width="Auto" /> | ||||
|                             </Grid.ColumnDefinitions> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="1" | ||||
|                                 Grid.Column="0" | ||||
|  | @ -897,6 +922,178 @@ | |||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin4}" /> | ||||
|                         </Grid> | ||||
| 
 | ||||
|                         <Grid | ||||
|                             Grid.Row="0" | ||||
|                             Grid.Column="1" | ||||
|                             Margin="{StaticResource Margin4}"> | ||||
|                             <Grid.RowDefinitions> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                             </Grid.RowDefinitions> | ||||
|                             <Grid.ColumnDefinitions> | ||||
|                                 <ColumnDefinition Width="Auto" /> | ||||
|                                 <ColumnDefinition Width="Auto" /> | ||||
|                             </Grid.ColumnDefinitions> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="0" | ||||
|                                 Grid.Column="0" | ||||
|                                 Grid.ColumnSpan="2" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Text="{x:Static resx:ResUI.TbSettingsSplitCoreDoc1}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="1" | ||||
|                                 Grid.Column="0" | ||||
|                                 Grid.ColumnSpan="2" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Text="{x:Static resx:ResUI.TbSettingsSplitCoreDoc2}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="2" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Text="{x:Static resx:ResUI.TbSettingsSplitCoreEnable}" /> | ||||
|                             <ToggleSwitch | ||||
|                                 x:Name="togCoreSplit" | ||||
|                                 Grid.Row="2" | ||||
|                                 Grid.Column="1" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 HorizontalAlignment="Left" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="3" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Text="路由 Core" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitRouteType" | ||||
|                                 Grid.Row="3" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="4" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Text="VMess" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType1" | ||||
|                                 Grid.Row="4" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="5" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Text="Shadowsocks" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType3" | ||||
|                                 Grid.Row="5" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="6" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Text="Socks" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType4" | ||||
|                                 Grid.Row="6" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="7" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Text="VLESS" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType5" | ||||
|                                 Grid.Row="7" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="8" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Text="Trojan" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType6" | ||||
|                                 Grid.Row="8" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="9" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Text="Hysteria2" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType7" | ||||
|                                 Grid.Row="9" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="10" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Text="TUIC" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType8" | ||||
|                                 Grid.Row="10" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin4}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="11" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin4}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Text="Wireguard" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType9" | ||||
|                                 Grid.Row="11" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin4}" /> | ||||
|                         </Grid> | ||||
|                     </Grid> | ||||
|                 </ScrollViewer> | ||||
|             </TabItem> | ||||
|         </TabControl> | ||||
|     </DockPanel> | ||||
|  |  | |||
|  | @ -41,6 +41,17 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel> | |||
|         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<OptionSettingViewModel> | |||
|             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); | ||||
|         }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -117,7 +117,7 @@ public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel> | |||
| 
 | ||||
|     private void linkdomainStrategy4Singbox_Click(object? sender, RoutedEventArgs e) | ||||
|     { | ||||
|         ProcUtils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/shared/listen/#domain_strategy"); | ||||
|         ProcUtils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/route/rule_action/#strategy"); | ||||
|     } | ||||
| 
 | ||||
|     private void btnCancel_Click(object? sender, RoutedEventArgs e) | ||||
|  |  | |||
|  | @ -707,6 +707,228 @@ | |||
|                         materialDesign:HintAssist.Hint="1500" | ||||
|                         Style="{StaticResource DefTextBox}" /> | ||||
|                 </Grid> | ||||
|                 <Grid | ||||
|                     x:Name="gridAnytls" | ||||
|                     Grid.Row="2" | ||||
|                     Visibility="Hidden"> | ||||
|                     <Grid.RowDefinitions> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                     </Grid.RowDefinitions> | ||||
|                     <Grid.ColumnDefinitions> | ||||
|                         <ColumnDefinition Width="180" /> | ||||
|                         <ColumnDefinition Width="Auto" /> | ||||
|                     </Grid.ColumnDefinitions> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Style="{StaticResource ToolbarTextBlock}" | ||||
|                         Text="{x:Static resx:ResUI.TbId3}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtId10" | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         Style="{StaticResource DefTextBox}" /> | ||||
|                 </Grid> | ||||
|                 <Grid | ||||
|                     x:Name="gridNaive" | ||||
|                     Grid.Row="2" | ||||
|                     Visibility="Hidden"> | ||||
|                     <Grid.RowDefinitions> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                     </Grid.RowDefinitions> | ||||
|                     <Grid.ColumnDefinitions> | ||||
|                         <ColumnDefinition Width="180" /> | ||||
|                         <ColumnDefinition Width="Auto" /> | ||||
|                     </Grid.ColumnDefinitions> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Style="{StaticResource ToolbarTextBlock}" | ||||
|                         Text="{x:Static resx:ResUI.TbId3}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtId100" | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         Style="{StaticResource DefTextBox}" /> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="3" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Style="{StaticResource ToolbarTextBlock}" | ||||
|                         Text="{x:Static resx:ResUI.TbHeaderType100}" /> | ||||
|                     <ComboBox | ||||
|                         x:Name="cmbHeaderType100" | ||||
|                         Grid.Row="3" | ||||
|                         Grid.Column="1" | ||||
|                         Width="200" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         Style="{StaticResource DefComboBox}" /> | ||||
|                 </Grid> | ||||
|                 <Grid | ||||
|                     x:Name="gridJuicity" | ||||
|                     Grid.Row="2" | ||||
|                     Visibility="Hidden"> | ||||
|                     <Grid.RowDefinitions> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                     </Grid.RowDefinitions> | ||||
|                     <Grid.ColumnDefinitions> | ||||
|                         <ColumnDefinition Width="180" /> | ||||
|                         <ColumnDefinition Width="Auto" /> | ||||
|                     </Grid.ColumnDefinitions> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Style="{StaticResource ToolbarTextBlock}" | ||||
|                         Text="{x:Static resx:ResUI.TbId}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtId101" | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         Style="{StaticResource DefTextBox}" /> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="2" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Style="{StaticResource ToolbarTextBlock}" | ||||
|                         Text="{x:Static resx:ResUI.TbId3}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtSecurity101" | ||||
|                         Grid.Row="2" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         Style="{StaticResource DefTextBox}" /> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="3" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Style="{StaticResource ToolbarTextBlock}" | ||||
|                         Text="{x:Static resx:ResUI.TbHeaderType8}" /> | ||||
|                     <ComboBox | ||||
|                         x:Name="cmbHeaderType101" | ||||
|                         Grid.Row="3" | ||||
|                         Grid.Column="1" | ||||
|                         Width="200" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         Style="{StaticResource DefComboBox}" /> | ||||
|                 </Grid> | ||||
|                 <Grid | ||||
|                     x:Name="gridBrook" | ||||
|                     Grid.Row="2" | ||||
|                     Visibility="Hidden"> | ||||
|                     <Grid.RowDefinitions> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                     </Grid.RowDefinitions> | ||||
|                     <Grid.ColumnDefinitions> | ||||
|                         <ColumnDefinition Width="180" /> | ||||
|                         <ColumnDefinition Width="Auto" /> | ||||
|                     </Grid.ColumnDefinitions> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Style="{StaticResource ToolbarTextBlock}" | ||||
|                         Text="{x:Static resx:ResUI.TbId3}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtId102" | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         Style="{StaticResource DefTextBox}" /> | ||||
|                 </Grid> | ||||
|                 <Grid | ||||
|                     x:Name="gridShadowquic" | ||||
|                     Grid.Row="2" | ||||
|                     Visibility="Hidden"> | ||||
|                     <Grid.RowDefinitions> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                         <RowDefinition Height="Auto" /> | ||||
|                     </Grid.RowDefinitions> | ||||
|                     <Grid.ColumnDefinitions> | ||||
|                         <ColumnDefinition Width="180" /> | ||||
|                         <ColumnDefinition Width="Auto" /> | ||||
|                     </Grid.ColumnDefinitions> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Style="{StaticResource ToolbarTextBlock}" | ||||
|                         Text="{x:Static resx:ResUI.TbId}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtId103" | ||||
|                         Grid.Row="1" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         Style="{StaticResource DefTextBox}" /> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="2" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Style="{StaticResource ToolbarTextBlock}" | ||||
|                         Text="{x:Static resx:ResUI.TbId3}" /> | ||||
|                     <TextBox | ||||
|                         x:Name="txtSecurity103" | ||||
|                         Grid.Row="2" | ||||
|                         Grid.Column="1" | ||||
|                         Width="400" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         Style="{StaticResource DefTextBox}" /> | ||||
| 
 | ||||
|                     <TextBlock | ||||
|                         Grid.Row="3" | ||||
|                         Grid.Column="0" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         VerticalAlignment="Center" | ||||
|                         Style="{StaticResource ToolbarTextBlock}" | ||||
|                         Text="{x:Static resx:ResUI.TbHeaderType8}" /> | ||||
|                     <ComboBox | ||||
|                         x:Name="cmbHeaderType103" | ||||
|                         Grid.Row="3" | ||||
|                         Grid.Column="1" | ||||
|                         Width="200" | ||||
|                         Margin="{StaticResource Margin4}" | ||||
|                         Style="{StaticResource DefComboBox}" /> | ||||
|                 </Grid> | ||||
| 
 | ||||
|                 <Separator | ||||
|                     x:Name="sepa2" | ||||
|  |  | |||
|  | @ -72,7 +72,7 @@ public partial class AddServerWindow | |||
|                 gridHysteria2.Visibility = Visibility.Visible; | ||||
|                 sepa2.Visibility = Visibility.Collapsed; | ||||
|                 gridTransport.Visibility = Visibility.Collapsed; | ||||
|                 cmbCoreType.IsEnabled = false; | ||||
|                 cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes.AppendEmpty(); | ||||
|                 cmbFingerprint.IsEnabled = false; | ||||
|                 cmbFingerprint.Text = string.Empty; | ||||
|                 break; | ||||
|  | @ -81,11 +81,11 @@ public partial class AddServerWindow | |||
|                 gridTuic.Visibility = Visibility.Visible; | ||||
|                 sepa2.Visibility = Visibility.Collapsed; | ||||
|                 gridTransport.Visibility = Visibility.Collapsed; | ||||
|                 cmbCoreType.IsEnabled = false; | ||||
|                 cmbCoreType.ItemsSource = Global.TuicCoreTypes.AppendEmpty(); | ||||
|                 cmbFingerprint.IsEnabled = false; | ||||
|                 cmbFingerprint.Text = string.Empty; | ||||
| 
 | ||||
|                 cmbHeaderType8.ItemsSource = Global.TuicCongestionControls; | ||||
|                 cmbHeaderType8.ItemsSource = Global.CongestionControls; | ||||
|                 break; | ||||
| 
 | ||||
|             case EConfigType.WireGuard: | ||||
|  | @ -96,6 +96,55 @@ public partial class AddServerWindow | |||
|                 gridTls.Visibility = Visibility.Collapsed; | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case EConfigType.Anytls: | ||||
|                 gridAnytls.Visibility = Visibility.Visible; | ||||
|                 cmbCoreType.IsEnabled = false; | ||||
|                 lstStreamSecurity.Add(Global.StreamSecurityReality); | ||||
|                 break; | ||||
| 
 | ||||
|             case EConfigType.NaiveProxy: | ||||
|                 gridNaive.Visibility = Visibility.Visible; | ||||
|                 sepa2.Visibility = Visibility.Collapsed; | ||||
|                 gridTransport.Visibility = Visibility.Collapsed; | ||||
|                 cmbAlpn.IsEnabled = false; | ||||
|                 cmbFingerprint.IsEnabled = false; | ||||
|                 cmbFingerprint.Text = string.Empty; | ||||
|                 cmbCoreType.IsEnabled = false; | ||||
| 
 | ||||
|                 cmbHeaderType100.ItemsSource = Global.NaiveProxyProtocols; | ||||
|                 break; | ||||
| 
 | ||||
|             case EConfigType.Juicity: | ||||
|                 gridJuicity.Visibility = Visibility.Visible; | ||||
|                 sepa2.Visibility = Visibility.Collapsed; | ||||
|                 gridTransport.Visibility = Visibility.Collapsed; | ||||
|                 cmbAlpn.IsEnabled = false; | ||||
|                 cmbFingerprint.IsEnabled = false; | ||||
|                 cmbFingerprint.Text = string.Empty; | ||||
|                 cmbCoreType.IsEnabled = false; | ||||
| 
 | ||||
|                 cmbHeaderType101.ItemsSource = Global.CongestionControls; | ||||
|                 break; | ||||
| 
 | ||||
|             case EConfigType.Brook: | ||||
|                 gridBrook.Visibility = Visibility.Visible; | ||||
|                 sepa2.Visibility = Visibility.Collapsed; | ||||
|                 gridTransport.Visibility = Visibility.Collapsed; | ||||
|                 gridTls.Visibility = Visibility.Collapsed; | ||||
|                 cmbCoreType.IsEnabled = false; | ||||
|                 break; | ||||
| 
 | ||||
|             case EConfigType.Shadowquic: | ||||
|                 gridShadowquic.Visibility = Visibility.Visible; | ||||
|                 sepa2.Visibility = Visibility.Collapsed; | ||||
|                 gridTransport.Visibility = Visibility.Collapsed; | ||||
|                 cmbFingerprint.IsEnabled = false; | ||||
|                 cmbFingerprint.Text = string.Empty; | ||||
|                 cmbCoreType.IsEnabled = false; | ||||
| 
 | ||||
|                 cmbHeaderType103.ItemsSource = Global.CongestionControls; | ||||
|                 break; | ||||
|         } | ||||
|         cmbStreamSecurity.ItemsSource = lstStreamSecurity; | ||||
| 
 | ||||
|  | @ -161,6 +210,31 @@ public partial class AddServerWindow | |||
|                     this.Bind(ViewModel, vm => vm.SelectedSource.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables); | ||||
|                     this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables); | ||||
|                     break; | ||||
| 
 | ||||
|                 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.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); | ||||
|  |  | |||
|  | @ -108,6 +108,27 @@ | |||
|                                     x:Name="menuAddTuicServer" | ||||
|                                     Height="{StaticResource MenuItemHeight}" | ||||
|                                     Header="{x:Static resx:ResUI.menuAddTuicServer}" /> | ||||
|                                 <MenuItem | ||||
|                                     x:Name="menuAddAnytlsServer" | ||||
|                                     Height="{StaticResource MenuItemHeight}" | ||||
|                                     Header="{x:Static resx:ResUI.menuAddAnytlsServer}" /> | ||||
|                                 <Separator Margin="-40,5" /> | ||||
|                                 <MenuItem | ||||
|                                     x:Name="menuAddBrookServer" | ||||
|                                     Height="{StaticResource MenuItemHeight}" | ||||
|                                     Header="{x:Static resx:ResUI.menuAddBrookServer}" /> | ||||
|                                 <MenuItem | ||||
|                                     x:Name="menuAddJuicityServer" | ||||
|                                     Height="{StaticResource MenuItemHeight}" | ||||
|                                     Header="{x:Static resx:ResUI.menuAddJuicityServer}" /> | ||||
|                                 <MenuItem | ||||
|                                     x:Name="menuAddNaiveServer" | ||||
|                                     Height="{StaticResource MenuItemHeight}" | ||||
|                                     Header="{x:Static resx:ResUI.menuAddNaiveServer}" /> | ||||
|                                 <MenuItem | ||||
|                                     x:Name="menuAddShadowquicServer" | ||||
|                                     Height="{StaticResource MenuItemHeight}" | ||||
|                                     Header="{x:Static resx:ResUI.menuAddShadowquicServer}" /> | ||||
|                             </MenuItem> | ||||
|                         </Menu> | ||||
|                         <Separator /> | ||||
|  |  | |||
|  | @ -80,6 +80,11 @@ public partial class MainWindow | |||
|             this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables); | ||||
|             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); | ||||
|  |  | |||
|  | @ -1107,7 +1107,19 @@ | |||
|             </TabItem> | ||||
| 
 | ||||
|             <TabItem Header="{x:Static resx:ResUI.TbSettingsCoreType}"> | ||||
|                 <ScrollViewer VerticalScrollBarVisibility="Visible"> | ||||
|                     <Grid Margin="{StaticResource Margin8}"> | ||||
|                         <Grid.RowDefinitions> | ||||
|                             <RowDefinition Height="Auto" /> | ||||
|                         </Grid.RowDefinitions> | ||||
|                         <Grid.ColumnDefinitions> | ||||
|                             <ColumnDefinition Width="Auto" /> | ||||
|                             <ColumnDefinition Width="Auto" /> | ||||
|                         </Grid.ColumnDefinitions> | ||||
|                         <Grid | ||||
|                             Grid.Row="0" | ||||
|                             Grid.Column="0" | ||||
|                             Margin="{StaticResource Margin8}"> | ||||
|                             <Grid.RowDefinitions> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|  | @ -1227,6 +1239,197 @@ | |||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 Style="{StaticResource DefComboBox}" /> | ||||
|                         </Grid> | ||||
|                         <Grid | ||||
|                             Grid.Row="0" | ||||
|                             Grid.Column="1" | ||||
|                             Margin="{StaticResource Margin8}"> | ||||
|                             <Grid.RowDefinitions> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                                 <RowDefinition Height="Auto" /> | ||||
|                             </Grid.RowDefinitions> | ||||
|                             <Grid.ColumnDefinitions> | ||||
|                                 <ColumnDefinition Width="Auto" /> | ||||
|                                 <ColumnDefinition Width="Auto" /> | ||||
|                             </Grid.ColumnDefinitions> | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="0" | ||||
|                                 Grid.Column="0" | ||||
|                                 Grid.ColumnSpan="2" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Style="{StaticResource ToolbarTextBlock}" | ||||
|                                 Text="{x:Static resx:ResUI.TbSettingsSplitCoreDoc1}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="1" | ||||
|                                 Grid.Column="0" | ||||
|                                 Grid.ColumnSpan="2" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Style="{StaticResource ToolbarTextBlock}" | ||||
|                                 Text="{x:Static resx:ResUI.TbSettingsSplitCoreDoc2}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="2" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Style="{StaticResource ToolbarTextBlock}" | ||||
|                                 Text="{x:Static resx:ResUI.TbSettingsSplitCoreEnable}" /> | ||||
|                             <ToggleButton | ||||
|                                 x:Name="togCoreSplit" | ||||
|                                 Grid.Row="2" | ||||
|                                 Grid.Column="1" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 HorizontalAlignment="Left" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="3" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Style="{StaticResource ToolbarTextBlock}" | ||||
|                                 Text="路由 Core" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitRouteType" | ||||
|                                 Grid.Row="3" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 Style="{StaticResource DefComboBox}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="4" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Style="{StaticResource ToolbarTextBlock}" | ||||
|                                 Text="VMess" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType1" | ||||
|                                 Grid.Row="4" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 Style="{StaticResource DefComboBox}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="5" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Style="{StaticResource ToolbarTextBlock}" | ||||
|                                 Text="Shadowsocks" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType3" | ||||
|                                 Grid.Row="5" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 Style="{StaticResource DefComboBox}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="6" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Style="{StaticResource ToolbarTextBlock}" | ||||
|                                 Text="Socks" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType4" | ||||
|                                 Grid.Row="6" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 Style="{StaticResource DefComboBox}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="7" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Style="{StaticResource ToolbarTextBlock}" | ||||
|                                 Text="VLESS" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType5" | ||||
|                                 Grid.Row="7" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 Style="{StaticResource DefComboBox}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="8" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Style="{StaticResource ToolbarTextBlock}" | ||||
|                                 Text="Trojan" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType6" | ||||
|                                 Grid.Row="8" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 Style="{StaticResource DefComboBox}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="9" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Style="{StaticResource ToolbarTextBlock}" | ||||
|                                 Text="Hysteria2" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType7" | ||||
|                                 Grid.Row="9" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 Style="{StaticResource DefComboBox}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="10" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Style="{StaticResource ToolbarTextBlock}" | ||||
|                                 Text="TUIC" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType8" | ||||
|                                 Grid.Row="10" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 Style="{StaticResource DefComboBox}" /> | ||||
| 
 | ||||
|                             <TextBlock | ||||
|                                 Grid.Row="11" | ||||
|                                 Grid.Column="0" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 VerticalAlignment="Center" | ||||
|                                 Style="{StaticResource ToolbarTextBlock}" | ||||
|                                 Text="Wireguard" /> | ||||
|                             <ComboBox | ||||
|                                 x:Name="cmbCoreSplitType9" | ||||
|                                 Grid.Row="11" | ||||
|                                 Grid.Column="1" | ||||
|                                 Width="200" | ||||
|                                 Margin="{StaticResource Margin8}" | ||||
|                                 Style="{StaticResource DefComboBox}" /> | ||||
|                         </Grid> | ||||
|                     </Grid> | ||||
|                 </ScrollViewer> | ||||
|             </TabItem> | ||||
|         </TabControl> | ||||
|     </DockPanel> | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -122,7 +122,7 @@ public partial class RoutingSettingWindow | |||
| 
 | ||||
|     private void linkdomainStrategy4Singbox_Click(object sender, RoutedEventArgs e) | ||||
|     { | ||||
|         ProcUtils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/shared/listen/#domain_strategy"); | ||||
|         ProcUtils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/route/rule_action/#strategy"); | ||||
|     } | ||||
| 
 | ||||
|     private void btnCancel_Click(object sender, System.Windows.RoutedEventArgs e) | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue