From ec00037526f056d35edb1e9d5cf3e48668de5463 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Wed, 23 Jul 2025 23:20:54 +0800 Subject: [PATCH 01/24] Add Minimal Core Config --- v2rayN/ServiceLib/Enums/EInboundProtocol.cs | 1 + .../CoreConfig/CoreConfigSingboxService.cs | 86 ++++++++++++++++++ .../CoreConfig/CoreConfigV2rayService.cs | 65 ++++++++++++++ .../Minimal/CoreConfigBrookService.cs | 50 +++++++++++ .../Minimal/CoreConfigHy2Service.cs | 88 +++++++++++++++++++ .../Minimal/CoreConfigJuicityService.cs | 72 +++++++++++++++ .../Minimal/CoreConfigNaiveService.cs | 53 +++++++++++ .../Minimal/CoreConfigShadowquicService.cs | 81 +++++++++++++++++ .../Minimal/CoreConfigTuicService.cs | 75 ++++++++++++++++ 9 files changed, 571 insertions(+) create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs diff --git a/v2rayN/ServiceLib/Enums/EInboundProtocol.cs b/v2rayN/ServiceLib/Enums/EInboundProtocol.cs index 768a428b..8c75c65f 100644 --- a/v2rayN/ServiceLib/Enums/EInboundProtocol.cs +++ b/v2rayN/ServiceLib/Enums/EInboundProtocol.cs @@ -9,5 +9,6 @@ public enum EInboundProtocol api, api2, mixed, + split, speedtest = 21 } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 80b89654..14a531d4 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -513,6 +513,92 @@ public class CoreConfigSingboxService } } + public async Task GeneratePureEndpointConfig(ProfileItem node) + { + var ret = new RetResult(); + try + { + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + return ret; + } + + ret.Msg = ResUI.InitialConfiguration; + + var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); + if (result.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + var singboxConfig = JsonUtils.Deserialize(result); + if (singboxConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + await GenLog(singboxConfig); + + var inbound = new Inbound4Sbox() + { + type = EInboundProtocol.mixed.ToString(), + tag = EInboundProtocol.socks.ToString(), + listen = Global.Loopback, + listen_port = AppHandler.Instance.GetLocalPort(EInboundProtocol.split) + }; + 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 = config.ToJsonString(new() { WriteIndented = true }); + + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + #endregion public gen function #region private gen function diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index 60b4df3e..c15c05ff 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -416,6 +416,71 @@ public class CoreConfigV2rayService } } + public async Task GeneratePureEndpointConfig(ProfileItem node) + { + var ret = new RetResult(); + try + { + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (node.GetNetwork() is nameof(ETransport.quic)) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + return ret; + } + + ret.Msg = ResUI.InitialConfiguration; + + var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); + if (result.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + var v2rayConfig = JsonUtils.Deserialize(result); + if (v2rayConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + await GenLog(v2rayConfig); + + var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.split, true); + + v2rayConfig.inbounds = new() { inbound }; + + 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 = config.ToJsonString(new() { WriteIndented = true }); + + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + #endregion public gen function #region private gen function diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs new file mode 100644 index 00000000..39e2bc5c --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs @@ -0,0 +1,50 @@ +namespace ServiceLib.Services.CoreConfig.Minimal; +public class CoreConfigBrookService +{ + private Config _config; + private static readonly string _tag = "CoreConfigBrookService"; + + public CoreConfigBrookService(Config config) + { + _config = config; + } + + public async Task GeneratePureEndpointConfig(ProfileItem node) + { + 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 + ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).ToString(); + + // outbound + processArgs += " --server " + node.Address + ":" + node.Port; + processArgs += " --password " + node.Id; + + ret.Data = processArgs; + + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return await Task.FromResult(ret); + } + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs new file mode 100644 index 00000000..25026dba --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs @@ -0,0 +1,88 @@ +using System.Text.Json.Nodes; + +namespace ServiceLib.Services.CoreConfig.Minimal; +public class CoreConfigHy2Service +{ + private Config _config; + private static readonly string _tag = "CoreConfigHy2Service"; + + public CoreConfigHy2Service(Config config) + { + _config = config; + } + + public async Task GeneratePureEndpointConfig(ProfileItem node) + { + 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 + ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).ToString() + }; + + // outbound + var port = string.Empty; + if (node.Ports.IsNotEmpty()) + { + port = node.Ports.Replace(':', '-'); + if (_config.HysteriaItem.HopInterval > 0) + { + configJsonNode["transport"] = new JsonObject + { + ["udp"] = new JsonObject + { + ["hopInterval"] = $"{_config.HysteriaItem.HopInterval}s" + } + }; + } + } + else + { + port = node.Port.ToString(); + } + configJsonNode["server"] = node.Address + ":" + port; + configJsonNode["auth"] = node.Id; + + 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.Data = configJsonNode.ToJsonString(new() { WriteIndented = true }); + + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return await Task.FromResult(ret); + } + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs new file mode 100644 index 00000000..14b6083f --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs @@ -0,0 +1,72 @@ +using System.Text.Json.Nodes; + +namespace ServiceLib.Services.CoreConfig.Minimal; +public class CoreConfigJuicityService +{ + private Config _config; + private static readonly string _tag = "CoreConfigJuicityService"; + + public CoreConfigJuicityService(Config config) + { + _config = config; + } + + public async Task GeneratePureEndpointConfig(ProfileItem node) + { + 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"] = ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).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"] = "bbr"; + + ret.Data = configJsonNode.ToJsonString(new() { WriteIndented = true }); + + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return await Task.FromResult(ret); + } + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs new file mode 100644 index 00000000..8307d5b0 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs @@ -0,0 +1,53 @@ +using System.Text.Json.Nodes; + +namespace ServiceLib.Services.CoreConfig.Minimal; +public class CoreConfigNaiveService +{ + private Config _config; + private static readonly string _tag = "CoreConfigNaiveService"; + + public CoreConfigNaiveService(Config config) + { + _config = config; + } + + public async Task GeneratePureEndpointConfig(ProfileItem node) + { + var ret = new RetResult(); + try + { + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + // TODO: EConfigType.Naive + + //if (node.ConfigType != EConfigType.Naive) + //{ + // ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; + // return ret; + //} + + var configJsonNode = new JsonObject(); + + // inbound + configJsonNode["listen"] = Global.SocksProtocol + Global.Loopback + ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).ToString(); + + // outbound + configJsonNode["proxy"] = (node.Network == "quic" ? "quic://" : Global.HttpsProtocol) + node.Id + "@" + node.Address + ":" + node.Port; + + ret.Data = configJsonNode.ToJsonString(new() { WriteIndented = true }); + + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return await Task.FromResult(ret); + } + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs new file mode 100644 index 00000000..3ca89444 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs @@ -0,0 +1,81 @@ +namespace ServiceLib.Services.CoreConfig.Minimal; +public class CoreConfigShadowquicService +{ + private Config _config; + private static readonly string _tag = "CoreConfigShadowquicService"; + + public CoreConfigShadowquicService(Config config) + { + _config = config; + } + + public async Task GeneratePureEndpointConfig(ProfileItem node) + { + var ret = new RetResult(); + try + { + if (node == null + || node.Port <= 0) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + //if (node.ConfigType != EConfigType.Shadowquic) + //{ + // ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; + // return ret; + //} + + var configYamlNode = new Dictionary(); + + // log + var logLevel = string.Empty; + switch (_config.CoreBasicItem.Loglevel) + { + case "warning": + logLevel = "warn"; + break; + default: + logLevel = _config.CoreBasicItem.Loglevel; + break; + } + configYamlNode["log-level"] = logLevel; + + // inbound + var inboundNode = new Dictionary + { + ["type"] = "socks5", + ["listen"] = Global.Loopback + ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).ToString() + }; + configYamlNode["inbound"] = inboundNode; + + // outbound + var outboundNode = new Dictionary + { + ["type"] = "shadowquic", + ["addr"] = node.Address + ":" + node.Port, + ["password"] = node.Id, + ["username"] = node.Security, + ["alpn"] = new List { "h3" }, + ["congestion-control"] = "bbr", + ["zero-rtt"] = true + }; + if (node.Sni.IsNotEmpty()) + { + outboundNode["server-name"] = node.Sni; + } + configYamlNode["outbound"] = outboundNode; + + ret.Data = YamlUtils.ToYaml(configYamlNode); + + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return await Task.FromResult(ret); + } + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs new file mode 100644 index 00000000..3f95bebd --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs @@ -0,0 +1,75 @@ +using System.Text.Json.Nodes; + +namespace ServiceLib.Services.CoreConfig.Minimal; +public class CoreConfigTuicService +{ + private Config _config; + private static readonly string _tag = "CoreConfigTuicService"; + + public CoreConfigTuicService(Config config) + { + _config = config; + } + + public async Task GeneratePureEndpointConfig(ProfileItem node) + { + 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 + ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).ToString() + }; + + // outbound + configJsonNode["relay"] = new JsonObject + { + ["server"] = node.Address + ":" + node.Port, + ["uuid"] = node.Id, + ["password"] = node.Security, + ["udp_relay_mode"] = "quic", + ["congestion_control"] = "bbr", + ["alpn"] = new JsonArray { "h3", "spdy/3.1" } + }; + + ret.Data = configJsonNode.ToJsonString(new() { WriteIndented = true }); + + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return await Task.FromResult(ret); + } + } +} From b460b0ff9155ef40b0c9e084d82c6c03831046d9 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 24 Jul 2025 00:17:26 +0800 Subject: [PATCH 02/24] Add Minimal Config Type --- v2rayN/ServiceLib/Enums/EConfigType.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/v2rayN/ServiceLib/Enums/EConfigType.cs b/v2rayN/ServiceLib/Enums/EConfigType.cs index 6698f962..71878b27 100644 --- a/v2rayN/ServiceLib/Enums/EConfigType.cs +++ b/v2rayN/ServiceLib/Enums/EConfigType.cs @@ -12,5 +12,9 @@ public enum EConfigType TUIC = 8, WireGuard = 9, HTTP = 10, - Anytls = 11 + Anytls = 11, + NaiveProxy = 100, + Juicity = 101, + Brook = 102, + Shadowquic = 103, } From c899d534afec79ff746447637be936989499844a Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 24 Jul 2025 10:56:25 +0800 Subject: [PATCH 03/24] Config and GUI --- v2rayN/ServiceLib/Global.cs | 24 ++ v2rayN/ServiceLib/Handler/AppHandler.cs | 11 + v2rayN/ServiceLib/Handler/ConfigHandler.cs | 7 + .../ServiceLib/Handler/CoreConfigHandler.cs | 33 ++ v2rayN/ServiceLib/Models/Config.cs | 1 + v2rayN/ServiceLib/Models/ConfigItems.cs | 8 + .../Minimal/CoreConfigBrookService.cs | 10 +- .../Minimal/CoreConfigJuicityService.cs | 10 +- .../Minimal/CoreConfigShadowquicService.cs | 10 +- .../ViewModels/OptionSettingViewModel.cs | 148 ++++++- v2rayN/v2rayN/Views/OptionSettingWindow.xaml | 398 +++++++++++++----- .../v2rayN/Views/OptionSettingWindow.xaml.cs | 23 + 12 files changed, 561 insertions(+), 122 deletions(-) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 4888ab6a..b12e1f50 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -278,6 +278,30 @@ public class Global "sing_box" ]; + public static readonly List Hysteria2CoreTypes = + [ + "sing_box", + "Hysteria2" + ]; + + public static readonly List TuicCoreTypes = + [ + "sing_box", + "TUIC" + ]; + + public static readonly List SupportSplitConfigTypes = + [ + EConfigType.VMess, + EConfigType.VLESS, + EConfigType.Shadowsocks, + EConfigType.Trojan, + EConfigType.Hysteria2, + EConfigType.TUIC, + EConfigType.WireGuard, + EConfigType.SOCKS, + ]; + public static readonly List DomainStrategies = [ "AsIs", diff --git a/v2rayN/ServiceLib/Handler/AppHandler.cs b/v2rayN/ServiceLib/Handler/AppHandler.cs index ad9a4029..f3b7e449 100644 --- a/v2rayN/ServiceLib/Handler/AppHandler.cs +++ b/v2rayN/ServiceLib/Handler/AppHandler.cs @@ -235,5 +235,16 @@ public sealed class AppHandler return item?.CoreType ?? ECoreType.Xray; } + public ECoreType GetSplitCoreType(ProfileItem profileItem, EConfigType eConfigType) + { + if (profileItem?.CoreType != null) + { + return (ECoreType)profileItem.CoreType; + } + + var item = _config.SplitCoreItem.SplitCoreTypes?.FirstOrDefault(it => it.ConfigType == eConfigType); + return item?.CoreType ?? ECoreType.Xray; + } + #endregion Core Type } diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 6c93d868..063e185a 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -165,6 +165,13 @@ public class ConfigHandler config.SystemProxyItem.SystemProxyExceptions = Utils.IsWindows() ? Global.SystemProxyExceptionsWindows : Global.SystemProxyExceptionsLinux; } + config.SplitCoreItem ??= new() + { + EnableSplitCore = false, + SplitCoreTypes = new List(), + RouteCoreType = ECoreType.Xray + }; + return config; } diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index 40c5eacf..0ee2e227 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -1,3 +1,5 @@ +using ServiceLib.Services.CoreConfig.Minimal; + namespace ServiceLib.Handler; /// @@ -41,6 +43,37 @@ public class CoreConfigHandler return result; } + public static async Task GeneratePureEndpointConfig(ProfileItem node, string? fileName) + { + var config = AppHandler.Instance.Config; + var result = new RetResult(); + + var coreType = AppHandler.Instance.GetSplitCoreType(node, node.ConfigType); + + result = coreType switch + { + ECoreType.sing_box => await new CoreConfigSingboxService(config).GeneratePureEndpointConfig(node), + ECoreType.Xray => await new CoreConfigV2rayService(config).GeneratePureEndpointConfig(node), + ECoreType.hysteria2 => await new CoreConfigHy2Service(config).GeneratePureEndpointConfig(node), + ECoreType.naiveproxy => await new CoreConfigNaiveService(config).GeneratePureEndpointConfig(node), + ECoreType.tuic => await new CoreConfigTuicService(config).GeneratePureEndpointConfig(node), + ECoreType.juicity => await new CoreConfigJuicityService(config).GeneratePureEndpointConfig(node), + ECoreType.brook => await new CoreConfigBrookService(config).GeneratePureEndpointConfig(node), + ECoreType.shadowquic => await new CoreConfigShadowquicService(config).GeneratePureEndpointConfig(node), + _ => throw new NotImplementedException(), + }; + + if (result.Success != true) + { + return result; + } + if (fileName.IsNotEmpty() && result.Data != null) + { + await File.WriteAllTextAsync(fileName, result.Data.ToString()); + } + return result; + } + private static async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) { var ret = new RetResult(); diff --git a/v2rayN/ServiceLib/Models/Config.cs b/v2rayN/ServiceLib/Models/Config.cs index 15996608..c6536f15 100644 --- a/v2rayN/ServiceLib/Models/Config.cs +++ b/v2rayN/ServiceLib/Models/Config.cs @@ -48,6 +48,7 @@ public class Config public List Inbound { get; set; } public List GlobalHotkeys { get; set; } public List CoreTypeItem { get; set; } + public SplitCoreItem SplitCoreItem { get; set; } #endregion other entities } diff --git a/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayN/ServiceLib/Models/ConfigItems.cs index c6254b9f..a8aa8244 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -138,6 +138,14 @@ public class CoreTypeItem public ECoreType CoreType { get; set; } } +[Serializable] +public class SplitCoreItem +{ + public bool EnableSplitCore { get; set; } + public List SplitCoreTypes { get; set; } + public ECoreType RouteCoreType { get; set; } +} + [Serializable] public class TunModeItem { diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs index 39e2bc5c..b3b4cef7 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs @@ -21,11 +21,11 @@ public class CoreConfigBrookService return ret; } - //if (node.ConfigType != EConfigType.Brook) - //{ - // ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; - // return ret; - //} + if (node.ConfigType != EConfigType.Brook) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; + return ret; + } var processArgs = "client"; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs index 14b6083f..a08b022b 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs @@ -23,11 +23,11 @@ public class CoreConfigJuicityService return ret; } - //if (node.ConfigType != EConfigType.Juicity) - //{ - // ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; - // return ret; - //} + if (node.ConfigType != EConfigType.Juicity) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; + return ret; + } var configJsonNode = new JsonObject(); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs index 3ca89444..1d224210 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs @@ -21,11 +21,11 @@ public class CoreConfigShadowquicService return ret; } - //if (node.ConfigType != EConfigType.Shadowquic) - //{ - // ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; - // return ret; - //} + if (node.ConfigType != EConfigType.Shadowquic) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; + return ret; + } var configYamlNode = new Dictionary(); diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index 57d8cac7..cf2252d7 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -1,6 +1,7 @@ using System.Reactive; using ReactiveUI; using ReactiveUI.Fody.Helpers; +using ServiceLib.Models; namespace ServiceLib.ViewModels; @@ -104,6 +105,21 @@ public class OptionSettingViewModel : MyReactiveObject #endregion CoreType + #region SplitCoreType + + [Reactive] public bool EnableSplitCore { get; set; } + [Reactive] public string SplitCoreType1 { get; set; } + [Reactive] public string SplitCoreType3 { get; set; } + [Reactive] public string SplitCoreType4 { get; set; } + [Reactive] public string SplitCoreType5 { get; set; } + [Reactive] public string SplitCoreType6 { get; set; } + [Reactive] public string SplitCoreType7 { get; set; } + [Reactive] public string SplitCoreType8 { get; set; } + [Reactive] public string SplitCoreType9 { get; set; } + [Reactive] public string RouteSplitCoreType { get; set; } + + #endregion SplitCoreType + public ReactiveCommand SaveCmd { get; } public OptionSettingViewModel(Func>? updateView) @@ -210,14 +226,13 @@ public class OptionSettingViewModel : MyReactiveObject #endregion Tun mode await InitCoreType(); + + await InitSplitCoreItem(); } private async Task InitCoreType() { - if (_config.CoreTypeItem == null) - { - _config.CoreTypeItem = new List(); - } + _config.CoreTypeItem ??= new List(); foreach (EConfigType it in Enum.GetValues(typeof(EConfigType))) { @@ -232,6 +247,7 @@ public class OptionSettingViewModel : MyReactiveObject CoreType = ECoreType.Xray }); } + _config.CoreTypeItem.ForEach(it => { var type = it.CoreType.ToString(); @@ -269,6 +285,87 @@ public class OptionSettingViewModel : MyReactiveObject await Task.CompletedTask; } + private async Task InitSplitCoreItem() + { + _config.SplitCoreItem ??= new() + { + EnableSplitCore = false, + SplitCoreTypes = new List(), + RouteCoreType = ECoreType.Xray + }; + + foreach (EConfigType it in Enum.GetValues(typeof(EConfigType))) + { + if (_config.SplitCoreItem.SplitCoreTypes.FindIndex(t => t.ConfigType == it) >= 0) + { + continue; + } + + if (it is EConfigType.Hysteria2 or EConfigType.TUIC) + { + _config.SplitCoreItem.SplitCoreTypes.Add(new CoreTypeItem() + { + ConfigType = it, + CoreType = ECoreType.sing_box + }); + continue; + } + if (it is EConfigType.Custom) + { + continue; + } + + _config.SplitCoreItem.SplitCoreTypes.Add(new CoreTypeItem() + { + ConfigType = it, + CoreType = ECoreType.Xray + }); + } + + EnableSplitCore = _config.SplitCoreItem.EnableSplitCore; + RouteSplitCoreType = _config.SplitCoreItem.RouteCoreType.ToString(); + + _config.SplitCoreItem.SplitCoreTypes.ForEach(it => + { + var type = it.CoreType.ToString(); + switch ((int)it.ConfigType) + { + case 1: + SplitCoreType1 = type; + break; + + case 3: + SplitCoreType3 = type; + break; + + case 4: + SplitCoreType4 = type; + break; + + case 5: + SplitCoreType5 = type; + break; + + case 6: + SplitCoreType6 = type; + break; + + case 7: + SplitCoreType7 = type; + break; + + case 8: + SplitCoreType8 = type; + break; + + case 9: + SplitCoreType9 = type; + break; + } + }); + await Task.CompletedTask; + } + private async Task SaveSettingAsync() { if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString()) @@ -362,6 +459,7 @@ public class OptionSettingViewModel : MyReactiveObject //coreType await SaveCoreType(); + await SaveSplitCoreType(); if (await ConfigHandler.SaveConfig(_config) == 0) { @@ -420,4 +518,46 @@ public class OptionSettingViewModel : MyReactiveObject } await Task.CompletedTask; } + + private async Task SaveSplitCoreType() + { + for (int k = 1; k <= _config.SplitCoreItem.SplitCoreTypes.Count; k++) + { + var item = _config.SplitCoreItem.SplitCoreTypes[k - 1]; + var type = string.Empty; + switch ((int)item.ConfigType) + { + case 1: + type = SplitCoreType1; + break; + case 3: + type = SplitCoreType3; + break; + case 4: + type = SplitCoreType4; + break; + case 5: + type = SplitCoreType5; + break; + case 6: + type = SplitCoreType6; + break; + case 7: + type = SplitCoreType7; + break; + case 8: + type = SplitCoreType8; + break; + case 9: + type = SplitCoreType9; + break; + default: + continue; + } + item.CoreType = (ECoreType)Enum.Parse(typeof(ECoreType), type); + } + _config.SplitCoreItem.RouteCoreType = (ECoreType)Enum.Parse(typeof(ECoreType), RouteSplitCoreType); + _config.SplitCoreItem.EnableSplitCore = EnableSplitCore; + await Task.CompletedTask; + } } diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index 890352fc..07d01f53 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -1110,122 +1110,314 @@ - - - - - - - - - + Margin="{StaticResource Margin8}"> + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + + + Margin="{StaticResource Margin8}"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs index c790e25d..889016bb 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs @@ -43,6 +43,17 @@ public partial class OptionSettingWindow cmbCoreType6.ItemsSource = Global.CoreTypes; cmbCoreType9.ItemsSource = Global.CoreTypes; + cmbCoreSplitRouteType.ItemsSource = Global.CoreTypes; + + cmbCoreSplitType1.ItemsSource = Global.CoreTypes; + cmbCoreSplitType3.ItemsSource = Global.CoreTypes; + cmbCoreSplitType4.ItemsSource = Global.CoreTypes; + cmbCoreSplitType5.ItemsSource = Global.CoreTypes; + cmbCoreSplitType6.ItemsSource = Global.CoreTypes; + cmbCoreSplitType7.ItemsSource = Global.Hysteria2CoreTypes; + cmbCoreSplitType8.ItemsSource = Global.TuicCoreTypes; + cmbCoreSplitType9.ItemsSource = Global.CoreTypes; + cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(2, 7).ToList(); cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).ToList(); cmbSpeedTestUrl.ItemsSource = Global.SpeedTestUrls; @@ -131,6 +142,18 @@ public partial class OptionSettingWindow this.Bind(ViewModel, vm => vm.CoreType6, v => v.cmbCoreType6.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.EnableSplitCore, v => v.togCoreSplit.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.RouteSplitCoreType, v => v.cmbCoreSplitRouteType.Text).DisposeWith(disposables); + + this.Bind(ViewModel, vm => vm.SplitCoreType1, v => v.cmbCoreSplitType1.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType3, v => v.cmbCoreSplitType3.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType4, v => v.cmbCoreSplitType4.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType5, v => v.cmbCoreSplitType5.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType6, v => v.cmbCoreSplitType6.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType7, v => v.cmbCoreSplitType7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType8, v => v.cmbCoreSplitType8.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType9, v => v.cmbCoreSplitType9.Text).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); }); WindowsUtils.SetDarkBorder(this, AppHandler.Instance.Config.UiItem.CurrentTheme); From b8dbe61db394a187f4e5a47a97a077dd300bb408 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 24 Jul 2025 12:48:52 +0800 Subject: [PATCH 04/24] Config Handler --- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 37 ---- v2rayN/ServiceLib/Handler/CoreHandler.cs | 190 ++++++++++++++++----- 2 files changed, 149 insertions(+), 78 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 063e185a..4e289bd4 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1197,43 +1197,6 @@ public class ConfigHandler return result; } - /// - /// Get a SOCKS server profile for pre-SOCKS functionality - /// Used when TUN mode is enabled or when a custom config has a pre-SOCKS port - /// - /// Current configuration - /// Server node that might need pre-SOCKS - /// Core type being used - /// A SOCKS profile item or null if not needed - public static async Task GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType) - { - ProfileItem? itemSocks = null; - if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun) - { - itemSocks = new ProfileItem() - { - CoreType = ECoreType.sing_box, - ConfigType = EConfigType.SOCKS, - Address = Global.Loopback, - Sni = node.Address, //Tun2SocksAddress - Port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks) - }; - } - else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)) - { - var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray; - itemSocks = new ProfileItem() - { - CoreType = preCoreType, - ConfigType = EConfigType.SOCKS, - Address = Global.Loopback, - Port = node.PreSocksPort.Value, - }; - } - await Task.CompletedTask; - return itemSocks; - } - /// /// Remove servers with invalid test results (timeout) /// Useful for cleaning up subscription lists diff --git a/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayN/ServiceLib/Handler/CoreHandler.cs index f7ad2285..b7a8b4f5 100644 --- a/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -1,5 +1,7 @@ using System.Diagnostics; using System.Text; +using ServiceLib.Enums; +using ServiceLib.Models; namespace ServiceLib.Handler; @@ -71,28 +73,24 @@ 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(); + context.AdjustForSplitCore(); + + // 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()}"); @@ -181,43 +179,153 @@ public class CoreHandler #region Private - private async Task CoreStart(ProfileItem node) + /// + /// Core launch context that encapsulates all parameters required for launching + /// + private class CoreLaunchContext { - var coreType = _config.RunningCoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); - var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); + 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; } - var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog; - var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true); - if (proc is null) + public CoreLaunchContext(ProfileItem node, Config config) { - return; + 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; } - _process = proc; - } - private async Task CoreStartPreService(ProfileItem node) - { - if (_process != null && !_process.HasExited) + /// + /// Adjust context parameters based on configuration type + /// + public void AdjustForConfigType() { - var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); - var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType); - if (itemSocks != null) + if (Node.ConfigType == EConfigType.Custom) { - var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box; - var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); - var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName); - if (result.Success) + SplitCore = false; + CoreType = Node.CoreType ?? ECoreType.Xray; + if (Node.PreSocksPort > 0) { - var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(preCoreType); - var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true); - if (proc is null) - { - return; - } - _processPre = proc; + PreCoreType = EnableTun ? ECoreType.sing_box : AppHandler.Instance.GetCoreType(Node, Node.ConfigType); + PreSocksPort = Node.PreSocksPort.Value; + } + else + { + EnableTun = false; + PreCoreType = null; } } } + + /// + /// Adjust split core configuration + /// + public void AdjustForSplitCore() + { + if (SplitCore) + { + PreCoreType = EnableTun ? ECoreType.sing_box : SplitRouteCore; + CoreType = PureEndpointCore; + + if (PreCoreType == CoreType) + { + PreCoreType = null; + SplitCore = false; + } + else + { + PreSocksPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.split); + } + } + } + } + + private async Task CoreStart(CoreLaunchContext context) + { + var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); + var result = context.SplitCore + ? await CoreConfigHandler.GeneratePureEndpointConfig(context.Node, fileName) + : await CoreConfigHandler.GenerateClientConfig(context.Node, 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, Global.CoreConfigFileName, displayLog, true); + + if (proc is null) + { + UpdateFunc(true, ResUI.FailedToRunCore); + return false; + } + + _process = proc; + _config.RunningCoreType = AppHandler.Instance.GetCoreType(context.Node, context.Node.ConfigType); + return true; + } + + private async Task CoreStartPreService(CoreLaunchContext context) + { + if (context.PreCoreType == null) + { + return true; // No pre-core needed, consider successful + } + + var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); + 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 result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName); + if (!result.Success) + { + UpdateFunc(true, result.Msg); + return false; + } + + var coreInfo = CoreInfoHandler.Instance.GetCoreInfo((ECoreType)context.PreCoreType); + var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, 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) From 0a2b4374f9732cf2f782bc0c4757f4a24c719f53 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 24 Jul 2025 12:51:17 +0800 Subject: [PATCH 05/24] Fixes --- v2rayN/ServiceLib/Global.cs | 4 +- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 4 +- v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 27 + v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 9 + v2rayN/ServiceLib/Resx/ResUI.hu.resx | 9 + v2rayN/ServiceLib/Resx/ResUI.resx | 9 + v2rayN/ServiceLib/Resx/ResUI.ru.resx | 9 + v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 9 + v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 9 + .../CoreConfig/CoreConfigSingboxService.cs | 3 +- .../CoreConfig/CoreConfigV2rayService.cs | 6 +- .../Minimal/CoreConfigBrookService.cs | 1 + .../Minimal/CoreConfigHy2Service.cs | 3 +- .../Minimal/CoreConfigJuicityService.cs | 5 +- .../Minimal/CoreConfigNaiveService.cs | 3 +- .../Minimal/CoreConfigShadowquicService.cs | 17 +- .../Minimal/CoreConfigTuicService.cs | 17 +- .../Views/AddServerWindow.axaml.cs | 6 +- .../Views/OptionSettingWindow.axaml | 375 ++++++++--- .../Views/OptionSettingWindow.axaml.cs | 23 + v2rayN/v2rayN/Views/AddServerWindow.xaml.cs | 6 +- v2rayN/v2rayN/Views/OptionSettingWindow.xaml | 593 +++++++++--------- 22 files changed, 748 insertions(+), 399 deletions(-) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index b12e1f50..c2001036 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -281,13 +281,13 @@ public class Global public static readonly List Hysteria2CoreTypes = [ "sing_box", - "Hysteria2" + "hysteria2" ]; public static readonly List TuicCoreTypes = [ "sing_box", - "TUIC" + "tuic" ]; public static readonly List SupportSplitConfigTypes = diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 4e289bd4..848d4f35 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -698,7 +698,7 @@ public class ConfigHandler public static async Task AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.Hysteria2; - profileItem.CoreType = ECoreType.sing_box; + //profileItem.CoreType = ECoreType.sing_box; profileItem.Address = profileItem.Address.TrimEx(); profileItem.Id = profileItem.Id.TrimEx(); @@ -731,7 +731,7 @@ public class ConfigHandler public static async Task AddTuicServer(Config config, ProfileItem profileItem, bool toFile = true) { profileItem.ConfigType = EConfigType.TUIC; - profileItem.CoreType = ECoreType.sing_box; + //profileItem.CoreType = ECoreType.sing_box; profileItem.Address = profileItem.Address.TrimEx(); profileItem.Id = profileItem.Id.TrimEx(); diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index ca554860..62ed61c2 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -3426,6 +3426,33 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. 的本地化字符串。 + /// + public static string TbSettingsSplitCoreDoc1 { + get { + return ResourceManager.GetString("TbSettingsSplitCoreDoc1", resourceCulture); + } + } + + /// + /// 查找类似 Routing Core defaults to sing-box when Tun is enabled. 的本地化字符串。 + /// + public static string TbSettingsSplitCoreDoc2 { + get { + return ResourceManager.GetString("TbSettingsSplitCoreDoc2", resourceCulture); + } + } + + /// + /// 查找类似 Enable separation of outbound and routing cores 的本地化字符串。 + /// + public static string TbSettingsSplitCoreEnable { + get { + return ResourceManager.GetString("TbSettingsSplitCoreEnable", resourceCulture); + } + } + /// /// 查找类似 sing-box ruleset files source (optional) 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index bd2cb887..ee776c78 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1404,4 +1404,13 @@ Add [Anytls] Configuration + + Enable separation of outbound and routing cores + + + Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. + + + Routing Core defaults to sing-box when Tun is enabled. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 0fc30153..6fdfc7dd 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1404,4 +1404,13 @@ [Anytls] konfiguráció hozzáadása + + Enable separation of outbound and routing cores + + + Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. + + + Routing Core defaults to sing-box when Tun is enabled. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 7ecfcc98..bffcea43 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1404,4 +1404,13 @@ Add [Anytls] Configuration + + Enable separation of outbound and routing cores + + + Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. + + + Routing Core defaults to sing-box when Tun is enabled. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 25f17e0d..42843dc4 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1404,4 +1404,13 @@ Добавить сервер [Anytls] + + Enable separation of outbound and routing cores + + + Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. + + + Routing Core defaults to sing-box when Tun is enabled. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 95d69e9b..a9ed87cc 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1401,4 +1401,13 @@ 添加 [Anytls] 配置文件 + + 启用出站核心与路由核心分离 + + + 出站与路由分离,出站与分流 Core 类型不同时,将启用两个核心 + + + 启用 Tun 时,路由 Core 为 sing-box + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 0b23885c..3e99f83a 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1401,4 +1401,13 @@ 新增 [Anytls] 設定檔 + + Enable separation of outbound and routing cores + + + Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. + + + Routing Core defaults to sing-box when Tun is enabled. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 14a531d4..ea5d787d 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -2,6 +2,7 @@ using System.Data; using System.Net; using System.Net.NetworkInformation; using System.Reactive; +using System.Text.Json.Nodes; using DynamicData; using ServiceLib.Models; @@ -587,7 +588,7 @@ public class CoreConfigSingboxService config.Remove("route"); - ret.Data = config.ToJsonString(new() { WriteIndented = true }); + ret.Data = JsonUtils.Serialize(config, true); return ret; } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index c15c05ff..966e23f5 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -453,6 +453,10 @@ public class CoreConfigV2rayService await GenLog(v2rayConfig); var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.split, true); + inbound.sniffing = new Sniffing4Ray + { + enabled = false + }; v2rayConfig.inbounds = new() { inbound }; @@ -469,7 +473,7 @@ public class CoreConfigV2rayService config.Remove("routing"); - ret.Data = config.ToJsonString(new() { WriteIndented = true }); + ret.Data = JsonUtils.Serialize(config, true); return ret; } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs index b3b4cef7..6ef1353e 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs @@ -36,6 +36,7 @@ public class CoreConfigBrookService processArgs += " --server " + node.Address + ":" + node.Port; processArgs += " --password " + node.Id; + ret.Success = true; ret.Data = processArgs; return await Task.FromResult(ret); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs index 25026dba..3241aecd 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs @@ -74,7 +74,8 @@ public class CoreConfigHy2Service configJsonNode["bandwidth"] = bandwidthObject; } - ret.Data = configJsonNode.ToJsonString(new() { WriteIndented = true }); + ret.Success = true; + ret.Data = JsonUtils.Serialize(configJsonNode, true); return await Task.FromResult(ret); } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs index a08b022b..66fccbbd 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs @@ -56,9 +56,10 @@ public class CoreConfigJuicityService configJsonNode["sni"] = node.Sni; } configJsonNode["allow_insecure"] = node.AllowInsecure == "true"; - configJsonNode["congestion_control"] = "bbr"; + configJsonNode["congestion_control"] = node.HeaderType; - ret.Data = configJsonNode.ToJsonString(new() { WriteIndented = true }); + ret.Success = true; + ret.Data = JsonUtils.Serialize(configJsonNode, true); return await Task.FromResult(ret); } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs index 8307d5b0..544e6f92 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs @@ -39,7 +39,8 @@ public class CoreConfigNaiveService // outbound configJsonNode["proxy"] = (node.Network == "quic" ? "quic://" : Global.HttpsProtocol) + node.Id + "@" + node.Address + ":" + node.Port; - ret.Data = configJsonNode.ToJsonString(new() { WriteIndented = true }); + ret.Success = true; + ret.Data = JsonUtils.Serialize(configJsonNode, true); return await Task.FromResult(ret); } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs index 1d224210..232b56df 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Nodes; + namespace ServiceLib.Services.CoreConfig.Minimal; public class CoreConfigShadowquicService { @@ -51,14 +53,24 @@ public class CoreConfigShadowquicService configYamlNode["inbound"] = inboundNode; // outbound + var alpn = new JsonArray(); + foreach (var item in node.GetAlpn() ?? new List()) + { + alpn.Add(item); + } + if (alpn.Count == 0) + { + alpn.Add("h3"); + } + var outboundNode = new Dictionary { ["type"] = "shadowquic", ["addr"] = node.Address + ":" + node.Port, ["password"] = node.Id, ["username"] = node.Security, - ["alpn"] = new List { "h3" }, - ["congestion-control"] = "bbr", + ["alpn"] = alpn, + ["congestion-control"] = node.HeaderType, ["zero-rtt"] = true }; if (node.Sni.IsNotEmpty()) @@ -67,6 +79,7 @@ public class CoreConfigShadowquicService } configYamlNode["outbound"] = outboundNode; + ret.Success = true; ret.Data = YamlUtils.ToYaml(configYamlNode); return await Task.FromResult(ret); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs index 3f95bebd..55dfecb1 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs @@ -51,17 +51,28 @@ public class CoreConfigTuicService }; // outbound + var alpn = new JsonArray(); + foreach(var item in node.GetAlpn() ?? new List()) + { + alpn.Add(item); + } + if (alpn.Count == 0) + { + alpn.Add("h3"); + } + configJsonNode["relay"] = new JsonObject { ["server"] = node.Address + ":" + node.Port, ["uuid"] = node.Id, ["password"] = node.Security, ["udp_relay_mode"] = "quic", - ["congestion_control"] = "bbr", - ["alpn"] = new JsonArray { "h3", "spdy/3.1" } + ["congestion_control"] = node.HeaderType, + ["alpn"] = alpn }; - ret.Data = configJsonNode.ToJsonString(new() { WriteIndented = true }); + ret.Success = true; + ret.Data = JsonUtils.Serialize(configJsonNode, true); return await Task.FromResult(ret); } diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs index 9d24175f..3a113722 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs @@ -78,7 +78,8 @@ public partial class AddServerWindow : WindowBase gridHysteria2.IsVisible = true; sepa2.IsVisible = false; gridTransport.IsVisible = false; - cmbCoreType.IsEnabled = false; + //cmbCoreType.IsEnabled = false; + cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes; cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; break; @@ -87,7 +88,8 @@ public partial class AddServerWindow : WindowBase gridTuic.IsVisible = true; sepa2.IsVisible = false; gridTransport.IsVisible = false; - cmbCoreType.IsEnabled = false; + //cmbCoreType.IsEnabled = false; + cmbCoreType.ItemsSource = Global.TuicCoreTypes; cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml index a75ed6fc..e61c16ea 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -802,101 +802,298 @@ - - - + + + + + + + + + - - + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs index 010488e5..152e629c 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs @@ -41,6 +41,17 @@ public partial class OptionSettingWindow : WindowBase cmbCoreType6.ItemsSource = Global.CoreTypes; cmbCoreType9.ItemsSource = Global.CoreTypes; + cmbCoreSplitRouteType.ItemsSource = Global.CoreTypes; + + cmbCoreSplitType1.ItemsSource = Global.CoreTypes; + cmbCoreSplitType3.ItemsSource = Global.CoreTypes; + cmbCoreSplitType4.ItemsSource = Global.CoreTypes; + cmbCoreSplitType5.ItemsSource = Global.CoreTypes; + cmbCoreSplitType6.ItemsSource = Global.CoreTypes; + cmbCoreSplitType7.ItemsSource = Global.Hysteria2CoreTypes; + cmbCoreSplitType8.ItemsSource = Global.TuicCoreTypes; + cmbCoreSplitType9.ItemsSource = Global.CoreTypes; + cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(2, 7).ToList(); cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).ToList(); cmbSpeedTestUrl.ItemsSource = Global.SpeedTestUrls; @@ -119,6 +130,18 @@ public partial class OptionSettingWindow : WindowBase this.Bind(ViewModel, vm => vm.CoreType6, v => v.cmbCoreType6.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.EnableSplitCore, v => v.togCoreSplit.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.RouteSplitCoreType, v => v.cmbCoreSplitRouteType.SelectedValue).DisposeWith(disposables); + + this.Bind(ViewModel, vm => vm.SplitCoreType1, v => v.cmbCoreSplitType1.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType3, v => v.cmbCoreSplitType3.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType4, v => v.cmbCoreSplitType4.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType5, v => v.cmbCoreSplitType5.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType6, v => v.cmbCoreSplitType6.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType7, v => v.cmbCoreSplitType7.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType8, v => v.cmbCoreSplitType8.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SplitCoreType9, v => v.cmbCoreSplitType9.SelectedValue).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); }); diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs b/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs index b06002a9..664a47bf 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs @@ -72,7 +72,8 @@ public partial class AddServerWindow gridHysteria2.Visibility = Visibility.Visible; sepa2.Visibility = Visibility.Collapsed; gridTransport.Visibility = Visibility.Collapsed; - cmbCoreType.IsEnabled = false; + //cmbCoreType.IsEnabled = false; + cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes; cmbFingerprint.IsEnabled = false; cmbFingerprint.Text = string.Empty; break; @@ -81,7 +82,8 @@ public partial class AddServerWindow gridTuic.Visibility = Visibility.Visible; sepa2.Visibility = Visibility.Collapsed; gridTransport.Visibility = Visibility.Collapsed; - cmbCoreType.IsEnabled = false; + //cmbCoreType.IsEnabled = false; + cmbCoreType.ItemsSource = Global.TuicCoreTypes; cmbFingerprint.IsEnabled = false; cmbFingerprint.Text = string.Empty; diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index 07d01f53..0710d65a 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -1107,318 +1107,329 @@ - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Margin="{StaticResource Margin8}"> + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + + + Margin="{StaticResource Margin8}"> + + + + + + + + + + + + + + + + + + + - - + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + - + From 896ec622328dca19ddcd7daa95f80dabfc7d5ae2 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 24 Jul 2025 15:28:51 +0800 Subject: [PATCH 06/24] Enable Tun SplitCore --- v2rayN/ServiceLib/Handler/CoreHandler.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayN/ServiceLib/Handler/CoreHandler.cs index b7a8b4f5..5e53e47b 100644 --- a/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -247,16 +247,30 @@ public class CoreHandler PreSocksPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.split); } } + else if (EnableTun) + { + PreCoreType = ECoreType.sing_box; + + if (PreCoreType != CoreType) // CoreType is xray + { + SplitCore = true; + PreSocksPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.split); + } + else // CoreType is sing_box + { + PreCoreType = null; + } + } } } private async Task CoreStart(CoreLaunchContext context) { var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); - var result = context.SplitCore - ? await CoreConfigHandler.GeneratePureEndpointConfig(context.Node, fileName) + var result = context.SplitCore + ? await CoreConfigHandler.GeneratePureEndpointConfig(context.Node, fileName) : await CoreConfigHandler.GenerateClientConfig(context.Node, fileName); - + if (result.Success != true) { UpdateFunc(true, result.Msg); From 2035a5ae618670172a744d5c76a22b2f78b6fedc Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 24 Jul 2025 15:35:21 +0800 Subject: [PATCH 07/24] Fixes --- .../CoreConfig/Minimal/CoreConfigNaiveService.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs index 544e6f92..f7a29230 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs @@ -23,13 +23,11 @@ public class CoreConfigNaiveService return ret; } - // TODO: EConfigType.Naive - - //if (node.ConfigType != EConfigType.Naive) - //{ - // ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; - // return ret; - //} + if (node.ConfigType != EConfigType.NaiveProxy) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}"; + return ret; + } var configJsonNode = new JsonObject(); From ac4692c7d08fb89c4a797bace24f03953c6ba180 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 24 Jul 2025 20:50:59 +0800 Subject: [PATCH 08/24] Fixes SplitCore == false and Node.CoreType == ECoreType.hysteria2 etc. logic --- v2rayN/ServiceLib/Handler/CoreHandler.cs | 28 +++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayN/ServiceLib/Handler/CoreHandler.cs index 5e53e47b..14530e05 100644 --- a/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -225,6 +225,24 @@ public class CoreHandler PreCoreType = null; } } + else if (!SplitCore && !EnableTun) + { + switch (Node.CoreType) + { + case ECoreType.hysteria2: + case ECoreType.tuic: + Node.CoreType = ECoreType.sing_box; + CoreType = ECoreType.sing_box; + break; + case ECoreType.v2fly: + case ECoreType.v2fly_v5: + Node.CoreType = ECoreType.Xray; + CoreType = ECoreType.Xray; + break; + default: + break; + } + } } /// @@ -251,15 +269,15 @@ public class CoreHandler { PreCoreType = ECoreType.sing_box; - if (PreCoreType != CoreType) // CoreType is xray + if (PreCoreType == CoreType) // CoreType is sing_box + { + PreCoreType = null; + } + else // CoreType is xray, hysteria2, tuic, etc. { SplitCore = true; PreSocksPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.split); } - else // CoreType is sing_box - { - PreCoreType = null; - } } } } From e156aaae340678192ff06560eaf1e69b3ff0815d Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 24 Jul 2025 21:26:52 +0800 Subject: [PATCH 09/24] Fixes (Prioritize Node.CoreType over SplitCore configuration) --- v2rayN/ServiceLib/Handler/AppHandler.cs | 63 ++++++++++++++++++++++++ v2rayN/ServiceLib/Handler/CoreHandler.cs | 60 ++-------------------- 2 files changed, 66 insertions(+), 57 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/AppHandler.cs b/v2rayN/ServiceLib/Handler/AppHandler.cs index f3b7e449..1422ad73 100644 --- a/v2rayN/ServiceLib/Handler/AppHandler.cs +++ b/v2rayN/ServiceLib/Handler/AppHandler.cs @@ -1,3 +1,7 @@ +using DynamicData; +using ServiceLib.Enums; +using ServiceLib.Models; + namespace ServiceLib.Handler; public sealed class AppHandler @@ -246,5 +250,64 @@ public sealed class AppHandler 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 = 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, profileItem.ConfigType); + } + else + { + preCoreType = null; + } + } + else if (!splitCore && profileItem.CoreType is not (ECoreType.Xray or ECoreType.sing_box)) + { + // Force SplitCore for cores that don't support direct routing (like Hysteria2, TUIC, etc.) + splitCore = true; + preCoreType = enableTun ? ECoreType.sing_box : splitRouteCore; + } + else if (splitCore) + { + // User explicitly enabled SplitCore + preCoreType = enableTun ? ECoreType.sing_box : splitRouteCore; + coreType = pureEndpointCore; + + if (preCoreType == coreType) + { + preCoreType = null; + splitCore = false; + } + } + else if (enableTun) // EnableTun is true but SplitCore is false + { + // TUN mode handling for Xray/sing_box cores + preCoreType = ECoreType.sing_box; + + if (preCoreType == coreType) // CoreType is sing_box + { + preCoreType = null; + } + else // CoreType is xray, etc. + { + // Force SplitCore for non-split cores + splitCore = true; + } + } + return (splitCore, coreType, preCoreType); + } + #endregion Core Type } diff --git a/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayN/ServiceLib/Handler/CoreHandler.cs index 14530e05..5b045238 100644 --- a/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -76,7 +76,6 @@ public class CoreHandler // Create launch context and configure parameters var context = new CoreLaunchContext(node, _config); context.AdjustForConfigType(); - context.AdjustForSplitCore(); // Start main core if (!await CoreStart(context)) @@ -210,74 +209,21 @@ public class CoreHandler /// public void AdjustForConfigType() { + (SplitCore, CoreType, PreCoreType) = AppHandler.Instance.GetCoreAndPreType(Node); if (Node.ConfigType == EConfigType.Custom) { - SplitCore = false; - CoreType = Node.CoreType ?? ECoreType.Xray; if (Node.PreSocksPort > 0) { - PreCoreType = EnableTun ? ECoreType.sing_box : AppHandler.Instance.GetCoreType(Node, Node.ConfigType); PreSocksPort = Node.PreSocksPort.Value; } else { EnableTun = false; - PreCoreType = null; } } - else if (!SplitCore && !EnableTun) + else if (PreCoreType != null) { - switch (Node.CoreType) - { - case ECoreType.hysteria2: - case ECoreType.tuic: - Node.CoreType = ECoreType.sing_box; - CoreType = ECoreType.sing_box; - break; - case ECoreType.v2fly: - case ECoreType.v2fly_v5: - Node.CoreType = ECoreType.Xray; - CoreType = ECoreType.Xray; - break; - default: - break; - } - } - } - - /// - /// Adjust split core configuration - /// - public void AdjustForSplitCore() - { - if (SplitCore) - { - PreCoreType = EnableTun ? ECoreType.sing_box : SplitRouteCore; - CoreType = PureEndpointCore; - - if (PreCoreType == CoreType) - { - PreCoreType = null; - SplitCore = false; - } - else - { - PreSocksPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.split); - } - } - else if (EnableTun) - { - PreCoreType = ECoreType.sing_box; - - if (PreCoreType == CoreType) // CoreType is sing_box - { - PreCoreType = null; - } - else // CoreType is xray, hysteria2, tuic, etc. - { - SplitCore = true; - PreSocksPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.split); - } + PreSocksPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.split); } } } From 85b6e5ba5030d3f725bb0469cbfb0c0eab490d01 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 24 Jul 2025 22:09:16 +0800 Subject: [PATCH 10/24] ShowClashUI --- v2rayN/ServiceLib/Handler/CoreHandler.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayN/ServiceLib/Handler/CoreHandler.cs index 5b045238..201b9ba6 100644 --- a/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Text; using ServiceLib.Enums; using ServiceLib.Models; +using static SQLite.SQLite3; namespace ServiceLib.Handler; @@ -265,7 +266,8 @@ public class CoreHandler } _process = proc; - _config.RunningCoreType = AppHandler.Instance.GetCoreType(context.Node, context.Node.ConfigType); + var (_, coreType, preCoreType) = AppHandler.Instance.GetCoreAndPreType(context.Node); + _config.RunningCoreType = (ECoreType)(preCoreType != null ? preCoreType : coreType); return true; } From 8f00d69e9dadef566754bd268acf526d6f08cf7f Mon Sep 17 00:00:00 2001 From: DHR60 Date: Mon, 28 Jul 2025 21:19:27 +0800 Subject: [PATCH 11/24] Fixes --- v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs | 6 ++---- v2rayN/v2rayN/Views/AddServerWindow.xaml.cs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs index 3a113722..39d6e68a 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs @@ -78,8 +78,7 @@ public partial class AddServerWindow : WindowBase gridHysteria2.IsVisible = true; sepa2.IsVisible = false; gridTransport.IsVisible = false; - //cmbCoreType.IsEnabled = false; - cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes; + cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes.AppendEmpty(); cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; break; @@ -88,8 +87,7 @@ public partial class AddServerWindow : WindowBase gridTuic.IsVisible = true; sepa2.IsVisible = false; gridTransport.IsVisible = false; - //cmbCoreType.IsEnabled = false; - cmbCoreType.ItemsSource = Global.TuicCoreTypes; + cmbCoreType.ItemsSource = Global.TuicCoreTypes.AppendEmpty(); cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs b/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs index 664a47bf..0e90ce0c 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs @@ -72,8 +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; + cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes.AppendEmpty(); cmbFingerprint.IsEnabled = false; cmbFingerprint.Text = string.Empty; break; @@ -82,8 +81,7 @@ public partial class AddServerWindow gridTuic.Visibility = Visibility.Visible; sepa2.Visibility = Visibility.Collapsed; gridTransport.Visibility = Visibility.Collapsed; - //cmbCoreType.IsEnabled = false; - cmbCoreType.ItemsSource = Global.TuicCoreTypes; + cmbCoreType.ItemsSource = Global.TuicCoreTypes.AppendEmpty(); cmbFingerprint.IsEnabled = false; cmbFingerprint.Text = string.Empty; From 59e7a5cdb90a99781e62bccc04af114fd357705b Mon Sep 17 00:00:00 2001 From: DHR60 Date: Mon, 28 Jul 2025 22:40:50 +0800 Subject: [PATCH 12/24] Try add GUI support --- v2rayN/ServiceLib/Global.cs | 29 ++- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 146 ++++++++++++- v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 45 ++++ v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 15 ++ v2rayN/ServiceLib/Resx/ResUI.hu.resx | 15 ++ v2rayN/ServiceLib/Resx/ResUI.resx | 15 ++ v2rayN/ServiceLib/Resx/ResUI.ru.resx | 15 ++ v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 15 ++ v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 15 ++ .../ViewModels/MainWindowViewModel.cs | 20 ++ .../Views/AddServerWindow.axaml.cs | 2 +- v2rayN/v2rayN/Views/AddServerWindow.xaml | 193 ++++++++++++++++++ v2rayN/v2rayN/Views/AddServerWindow.xaml.cs | 66 +++++- v2rayN/v2rayN/Views/MainWindow.xaml | 17 ++ v2rayN/v2rayN/Views/MainWindow.xaml.cs | 4 + 15 files changed, 607 insertions(+), 5 deletions(-) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index c2001036..e1daa015 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -290,6 +290,26 @@ public class Global "tuic" ]; + public static readonly List NaiveProxyCoreTypes = + [ + "naiveproxy" + ]; + + public static readonly List JuicityProxyCoreTypes = + [ + "juicity" + ]; + + public static readonly List BrookCoreTypes = + [ + "brook" + ]; + + public static readonly List ShadowquicCoreTypes = + [ + "shadowquic" + ]; + public static readonly List SupportSplitConfigTypes = [ EConfigType.VMess, @@ -488,13 +508,20 @@ public class Global "" ]; - public static readonly List TuicCongestionControls = + public static readonly List CongestionControls = [ "cubic", "new_reno", "bbr" ]; + public static readonly List NaiveProxyProtocols = + [ + "https", + "http", + "quic" + ]; + public static readonly List allowSelectType = [ "selector", diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 848d4f35..ade49c61 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -270,6 +270,10 @@ public class ConfigHandler EConfigType.TUIC => await AddTuicServer(config, item), EConfigType.WireGuard => await AddWireguardServer(config, item), EConfigType.Anytls => await AddAnytlsServer(config, item), + EConfigType.NaiveProxy => await AddNaiveServer(config, item), + EConfigType.Juicity => await AddJuicityServer(config, item), + EConfigType.Brook => await AddBrookServer(config, item), + EConfigType.Shadowquic => await AddShadowquicServer(config, item), _ => -1, }; return ret; @@ -738,9 +742,9 @@ public class ConfigHandler 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()) @@ -823,6 +827,140 @@ public class ConfigHandler return 0; } + /// + /// Add or edit a Naive server + /// Validates and processes Naive-specific settings + /// + /// Current configuration + /// Naive profile to add + /// Whether to save to file + /// 0 if successful, -1 if failed + public static async Task AddNaiveServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.NaiveProxy; + profileItem.CoreType = ECoreType.naiveproxy; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Network = string.Empty; + if (profileItem.StreamSecurity.IsNullOrEmpty()) + { + profileItem.StreamSecurity = Global.StreamSecurity; + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + await AddServerCommon(config, profileItem, toFile); + return 0; + } + + /// + /// Add or edit a Juicity server + /// Validates and processes Juicity-specific settings + /// + /// Current configuration + /// Juicity profile to add + /// Whether to save to file + /// 0 if successful, -1 if failed + public static async Task AddJuicityServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.Juicity; + profileItem.CoreType = ECoreType.juicity; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Network = string.Empty; + + if (!Global.CongestionControls.Contains(profileItem.HeaderType)) + { + profileItem.HeaderType = Global.CongestionControls.FirstOrDefault()!; + } + + if (profileItem.StreamSecurity.IsNullOrEmpty()) + { + profileItem.StreamSecurity = Global.StreamSecurity; + } + if (profileItem.Alpn.IsNullOrEmpty()) + { + profileItem.Alpn = "h3"; + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + + /// + /// Add or edit a Brook server + /// Validates and processes Brook-specific settings + /// + /// Current configuration + /// Brook profile to add + /// Whether to save to file + /// 0 if successful, -1 if failed + public static async Task AddBrookServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.Brook; + profileItem.CoreType = ECoreType.brook; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Network = string.Empty; + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + await AddServerCommon(config, profileItem, toFile); + return 0; + } + + /// + /// Add or edit a Shadowquic server + /// Validates and processes Shadowquic-specific settings + /// + /// Current configuration + /// Shadowquic profile to add + /// Whether to save to file + /// 0 if successful, -1 if failed + public static async Task AddShadowquicServer(Config config, ProfileItem profileItem, bool toFile = true) + { + profileItem.ConfigType = EConfigType.Shadowquic; + profileItem.CoreType = ECoreType.shadowquic; + + profileItem.Address = profileItem.Address.TrimEx(); + profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Network = string.Empty; + + if (!Global.CongestionControls.Contains(profileItem.HeaderType)) + { + profileItem.HeaderType = Global.CongestionControls.FirstOrDefault()!; + } + + if (profileItem.StreamSecurity.IsNullOrEmpty()) + { + profileItem.StreamSecurity = Global.StreamSecurity; + } + if (profileItem.Alpn.IsNullOrEmpty()) + { + profileItem.Alpn = "h3"; + } + if (profileItem.Id.IsNullOrEmpty()) + { + return -1; + } + + await AddServerCommon(config, profileItem, toFile); + + return 0; + } + /// /// Sort the server list by the specified column /// Updates the sort order in the profile extension data @@ -1296,6 +1434,10 @@ public class ConfigHandler EConfigType.TUIC => await AddTuicServer(config, profileItem, false), EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false), EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false), + EConfigType.NaiveProxy => await AddNaiveServer(config, profileItem, false), + EConfigType.Juicity => await AddJuicityServer(config, profileItem, false), + EConfigType.Brook => await AddBrookServer(config, profileItem, false), + EConfigType.Shadowquic => await AddShadowquicServer(config, profileItem, false), _ => -1, }; diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 62ed61c2..b998e8b4 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -663,6 +663,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Add [Brook] Configuration 的本地化字符串。 + /// + public static string menuAddBrookServer { + get { + return ResourceManager.GetString("menuAddBrookServer", resourceCulture); + } + } + /// /// 查找类似 Add a custom configuration Configuration 的本地化字符串。 /// @@ -690,6 +699,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Add [Juicity] Configuration 的本地化字符串。 + /// + public static string menuAddJuicityServer { + get { + return ResourceManager.GetString("menuAddJuicityServer", resourceCulture); + } + } + + /// + /// 查找类似 Add [Naive] Configuration 的本地化字符串。 + /// + public static string menuAddNaiveServer { + get { + return ResourceManager.GetString("menuAddNaiveServer", resourceCulture); + } + } + /// /// 查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。 /// @@ -717,6 +744,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Add [Shadowquic] Configuration 的本地化字符串。 + /// + public static string menuAddShadowquicServer { + get { + return ResourceManager.GetString("menuAddShadowquicServer", resourceCulture); + } + } + /// /// 查找类似 Add [Shadowsocks] Configuration 的本地化字符串。 /// @@ -2472,6 +2508,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Proxy Protocol 的本地化字符串。 + /// + public static string TbHeaderType100 { + get { + return ResourceManager.GetString("TbHeaderType100", resourceCulture); + } + } + /// /// 查找类似 Congestion control 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index ee776c78..aee7a852 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1413,4 +1413,19 @@ Routing Core defaults to sing-box when Tun is enabled. + + افزودن سرور [Brook] + + + افزودن سرور [Juicity] + + + افزودن سرور [Naive] + + + افزودن سرور [Shadowquic] + + + Proxy Protocol + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 6fdfc7dd..167f3b88 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1413,4 +1413,19 @@ Routing Core defaults to sing-box when Tun is enabled. + + [Brook] szerver hozzáadása + + + [Juicity] szerver hozzáadása + + + [Naive] szerver hozzáadása + + + [Shadowquic] szerver hozzáadása + + + Proxy Protocol + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index bffcea43..eaf705b6 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1413,4 +1413,19 @@ Routing Core defaults to sing-box when Tun is enabled. + + Add [Brook] Configuration + + + Add [Juicity] Configuration + + + Add [Naive] Configuration + + + Add [Shadowquic] Configuration + + + Proxy Protocol + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 42843dc4..bb855dfe 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1413,4 +1413,19 @@ Routing Core defaults to sing-box when Tun is enabled. + + Добавить сервер [Brook] + + + Добавить сервер [Juicity] + + + Добавить сервер [Naive] + + + Добавить сервер [Shadowquic] + + + Proxy Protocol + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index a9ed87cc..fa4f1e8b 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1410,4 +1410,19 @@ 启用 Tun 时,路由 Core 为 sing-box + + 添加 [Brook] 配置文件 + + + 添加 [Juicity] 配置文件 + + + 添加 [Naive] 配置文件 + + + 添加 [Shadowquic] 配置文件 + + + 代理协议 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 3e99f83a..ebea5465 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1410,4 +1410,19 @@ Routing Core defaults to sing-box when Tun is enabled. + + 新增 [Brook] 設定檔 + + + 新增 [Juicity] 設定檔 + + + 新增 [Naive] 設定檔 + + + 新增 [Shadowquic] 設定檔 + + + Proxy Protocol + \ No newline at end of file diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index 36e20a87..0dc991cb 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -25,6 +25,10 @@ public class MainWindowViewModel : MyReactiveObject public ReactiveCommand AddServerViaClipboardCmd { get; } public ReactiveCommand AddServerViaScanCmd { get; } public ReactiveCommand AddServerViaImageCmd { get; } + public ReactiveCommand AddBrookServerCmd { get; } + public ReactiveCommand AddJuicityServerCmd { get; } + public ReactiveCommand AddNaiveServerCmd { get; } + public ReactiveCommand AddShadowquicServerCmd { get; } //Subscription public ReactiveCommand SubSettingCmd { get; } @@ -116,6 +120,22 @@ public class MainWindowViewModel : MyReactiveObject { await AddServerAsync(true, EConfigType.Anytls); }); + AddBrookServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.Brook); + }); + AddJuicityServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.Juicity); + }); + AddNaiveServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.NaiveProxy); + }); + AddShadowquicServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.Shadowquic); + }); AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.Custom); diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs index 39d6e68a..fae84814 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs @@ -91,7 +91,7 @@ public partial class AddServerWindow : WindowBase cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; - cmbHeaderType8.ItemsSource = Global.TuicCongestionControls; + cmbHeaderType8.ItemsSource = Global.CongestionControls; break; case EConfigType.WireGuard: diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayN/v2rayN/Views/AddServerWindow.xaml index acea0c28..2f5c73c0 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -736,6 +736,199 @@ Margin="{StaticResource Margin4}" Style="{StaticResource DefTextBox}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables); break; + + case EConfigType.NaiveProxy: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId100.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType100.Text).DisposeWith(disposables); + break; + + case EConfigType.Juicity: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId101.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity101.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType101.Text).DisposeWith(disposables); + break; + + case EConfigType.Brook: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId102.Text).DisposeWith(disposables); + break; + + case EConfigType.Shadowquic: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId103.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity103.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType103.Text).DisposeWith(disposables); + break; } this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.Text).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml b/v2rayN/v2rayN/Views/MainWindow.xaml index 0b8c30b3..59b6e6ed 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml +++ b/v2rayN/v2rayN/Views/MainWindow.xaml @@ -112,6 +112,23 @@ x:Name="menuAddAnytlsServer" Height="{StaticResource MenuItemHeight}" Header="{x:Static resx:ResUI.menuAddAnytlsServer}" /> + + + + + diff --git a/v2rayN/v2rayN/Views/MainWindow.xaml.cs b/v2rayN/v2rayN/Views/MainWindow.xaml.cs index 08bdd90b..b4d41965 100644 --- a/v2rayN/v2rayN/Views/MainWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/MainWindow.xaml.cs @@ -81,6 +81,10 @@ public partial class MainWindow this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddBrookServerCmd, v => v.menuAddBrookServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddJuicityServerCmd, v => v.menuAddJuicityServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddNaiveServerCmd, v => v.menuAddNaiveServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddShadowquicServerCmd, v => v.menuAddShadowquicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables); From 6e750b4c034117ab4f6111972d007f65745a1f71 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Mon, 28 Jul 2025 22:49:46 +0800 Subject: [PATCH 13/24] avalonia --- .../Views/AddServerWindow.axaml | 145 ++++++++++++++++++ .../Views/AddServerWindow.axaml.cs | 64 ++++++++ v2rayN/v2rayN.Desktop/Views/MainWindow.axaml | 5 + .../v2rayN.Desktop/Views/MainWindow.axaml.cs | 4 + 4 files changed, 218 insertions(+) diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index 56cc82a0..84f00a4d 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -553,6 +553,151 @@ Width="400" Margin="{StaticResource Margin4}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + lstStreamSecurity.Add(Global.StreamSecurityReality); cmbCoreType.IsEnabled = false; break; + + case EConfigType.NaiveProxy: + gridNaive.IsVisible = true; + sepa2.IsVisible = false; + gridTransport.IsVisible = false; + cmbAlpn.IsEnabled = false; + cmbFingerprint.IsEnabled = false; + cmbFingerprint.SelectedValue = string.Empty; + cmbCoreType.IsEnabled = false; + + cmbHeaderType100.ItemsSource = Global.NaiveProxyProtocols; + break; + + case EConfigType.Juicity: + gridJuicity.IsVisible = true; + sepa2.IsVisible = false; + gridTransport.IsVisible = false; + cmbAlpn.IsEnabled = false; + cmbFingerprint.IsEnabled = false; + cmbFingerprint.SelectedValue = string.Empty; + cmbCoreType.IsEnabled = false; + + cmbHeaderType101.ItemsSource = Global.CongestionControls; + break; + + case EConfigType.Brook: + gridBrook.IsVisible = true; + sepa2.IsVisible = false; + gridTransport.IsVisible = false; + gridTls.IsVisible = false; + cmbCoreType.IsEnabled = false; + break; + + case EConfigType.Shadowquic: + gridShadowquic.IsVisible = true; + sepa2.IsVisible = false; + gridTransport.IsVisible = false; + cmbFingerprint.IsEnabled = false; + cmbFingerprint.SelectedValue = string.Empty; + cmbCoreType.IsEnabled = false; + + cmbHeaderType103.ItemsSource = Global.CongestionControls; + break; } cmbStreamSecurity.ItemsSource = lstStreamSecurity; @@ -177,6 +220,27 @@ public partial class AddServerWindow : WindowBase case EConfigType.Anytls: this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables); break; + + case EConfigType.NaiveProxy: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId100.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType100.SelectedValue).DisposeWith(disposables); + break; + + case EConfigType.Juicity: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId101.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity101.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType101.SelectedValue).DisposeWith(disposables); + break; + + case EConfigType.Brook: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId102.Text).DisposeWith(disposables); + break; + + case EConfigType.Shadowquic: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId103.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity103.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType103.SelectedValue).DisposeWith(disposables); + break; } this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.SelectedValue).DisposeWith(disposables); diff --git a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml index af4ae529..9054ad69 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml @@ -47,6 +47,11 @@ + + + + + diff --git a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs index 2584310b..67bb67f3 100644 --- a/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs @@ -84,6 +84,10 @@ public partial class MainWindow : WindowBase this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddBrookServerCmd, v => v.menuAddBrookServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddJuicityServerCmd, v => v.menuAddJuicityServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddNaiveServerCmd, v => v.menuAddNaiveServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddShadowquicServerCmd, v => v.menuAddShadowquicServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables); From 37a5b7f3b4def925638fe379e0daec1cbfbdb946 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Mon, 28 Jul 2025 22:53:05 +0800 Subject: [PATCH 14/24] Fixes NaiveProxy --- v2rayN/ServiceLib/Handler/ConfigHandler.cs | 4 ++++ .../Services/CoreConfig/Minimal/CoreConfigNaiveService.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index ade49c61..ab19578c 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -843,6 +843,10 @@ public class ConfigHandler 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; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs index f7a29230..0e1230cc 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs @@ -35,7 +35,7 @@ public class CoreConfigNaiveService configJsonNode["listen"] = Global.SocksProtocol + Global.Loopback + ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).ToString(); // outbound - configJsonNode["proxy"] = (node.Network == "quic" ? "quic://" : Global.HttpsProtocol) + node.Id + "@" + node.Address + ":" + node.Port; + configJsonNode["proxy"] = (node.HeaderType == "quic" ? "quic://" : Global.HttpsProtocol) + node.Id + "@" + node.Address + ":" + node.Port; ret.Success = true; ret.Data = JsonUtils.Serialize(configJsonNode, true); From 8e177a98512a7418ae4b00f02967f283f04faa5a Mon Sep 17 00:00:00 2001 From: DHR60 Date: Mon, 28 Jul 2025 23:28:20 +0800 Subject: [PATCH 15/24] Add Naive etc. uri share URI sharing is not compatible with other clients. Compatibility was not part of the original design and it is intended solely for internal sharing and copying within the application. --- v2rayN/ServiceLib/Global.cs | 12 +++- v2rayN/ServiceLib/Handler/Fmt/BrookFmt.cs | 49 +++++++++++++++ v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs | 20 ++++++ v2rayN/ServiceLib/Handler/Fmt/JuicityFmt.cs | 63 +++++++++++++++++++ v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs | 52 +++++++++++++++ .../ServiceLib/Handler/Fmt/ShadowquicFmt.cs | 63 +++++++++++++++++++ 6 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 v2rayN/ServiceLib/Handler/Fmt/BrookFmt.cs create mode 100644 v2rayN/ServiceLib/Handler/Fmt/JuicityFmt.cs create mode 100644 v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs create mode 100644 v2rayN/ServiceLib/Handler/Fmt/ShadowquicFmt.cs diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index e1daa015..e7a2cd27 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -170,7 +170,11 @@ public class Global { EConfigType.Hysteria2, "hysteria2://" }, { EConfigType.TUIC, "tuic://" }, { EConfigType.WireGuard, "wireguard://" }, - { EConfigType.Anytls, "anytls://" } + { EConfigType.Anytls, "anytls://" }, + { EConfigType.NaiveProxy, "naive://" }, + { EConfigType.Juicity, "juicity://" }, + { EConfigType.Brook, "brook://" }, + { EConfigType.Shadowquic, "shadowquic://" } }; public static readonly Dictionary ProtocolTypes = new() @@ -184,7 +188,11 @@ public class Global { EConfigType.Hysteria2, "hysteria2" }, { EConfigType.TUIC, "tuic" }, { EConfigType.WireGuard, "wireguard" }, - { EConfigType.Anytls, "anytls" } + { EConfigType.Anytls, "anytls" }, + { EConfigType.NaiveProxy, "naiveproxy" }, + { EConfigType.Juicity, "juicity" }, + { EConfigType.Brook, "brook" }, + { EConfigType.Shadowquic, "shadowquic" } }; public static readonly List VmessSecurities = diff --git a/v2rayN/ServiceLib/Handler/Fmt/BrookFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/BrookFmt.cs new file mode 100644 index 00000000..4bde23b5 --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Fmt/BrookFmt.cs @@ -0,0 +1,49 @@ +using static QRCoder.PayloadGenerator; + +namespace ServiceLib.Handler.Fmt; +public class BrookFmt : BaseFmt +{ + public static ProfileItem? Resolve(string str, out string msg) + { + msg = ResUI.ConfigurationFormatIncorrect; + + var parsedUrl = Utils.TryUri(str); + if (parsedUrl == null) + { + return null; + } + + ProfileItem item = new() + { + ConfigType = EConfigType.Brook, + Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), + Address = parsedUrl.IdnHost, + Port = parsedUrl.Port, + }; + var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); + item.Id = rawUserInfo; + + var query = Utils.ParseQueryString(parsedUrl.Query); + _ = ResolveStdTransport(query, ref item); + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; + } + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var pw = item.Id; + var dicQuery = new Dictionary(); + _ = GetStdTransport(item, Global.None, ref dicQuery); + + return ToUri(EConfigType.Brook, item.Address, item.Port, pw, dicQuery, remark); + } +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs index 814d753d..41e112ff 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs @@ -19,6 +19,10 @@ public class FmtHandler EConfigType.TUIC => TuicFmt.ToUri(item), EConfigType.WireGuard => WireguardFmt.ToUri(item), EConfigType.Anytls => AnytlsFmt.ToUri(item), + EConfigType.NaiveProxy => NaiveFmt.ToUri(item), + EConfigType.Juicity => JuicityFmt.ToUri(item), + EConfigType.Brook => BrookFmt.ToUri(item), + EConfigType.Shadowquic => ShadowquicFmt.ToUri(item), _ => null, }; @@ -80,6 +84,22 @@ public class FmtHandler { return AnytlsFmt.Resolve(str, out msg); } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.NaiveProxy])) + { + return NaiveFmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Juicity])) + { + return JuicityFmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Brook])) + { + return BrookFmt.Resolve(str, out msg); + } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Shadowquic])) + { + return ShadowquicFmt.Resolve(str, out msg); + } else { msg = ResUI.NonvmessOrssProtocol; diff --git a/v2rayN/ServiceLib/Handler/Fmt/JuicityFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/JuicityFmt.cs new file mode 100644 index 00000000..d6cda203 --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Fmt/JuicityFmt.cs @@ -0,0 +1,63 @@ +namespace ServiceLib.Handler.Fmt; + +public class JuicityFmt : BaseFmt +{ + public static ProfileItem? Resolve(string str, out string msg) + { + msg = ResUI.ConfigurationFormatIncorrect; + + ProfileItem item = new() + { + ConfigType = EConfigType.Juicity + }; + + var url = Utils.TryUri(str); + if (url == null) + { + return null; + } + + item.Address = url.IdnHost; + item.Port = url.Port; + item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + var rawUserInfo = Utils.UrlDecode(url.UserInfo); + var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2); + if (userInfoParts.Length == 2) + { + item.Id = userInfoParts.First(); + item.Security = userInfoParts.Last(); + } + + var query = Utils.ParseQueryString(url.Query); + ResolveStdTransport(query, ref item); + item.HeaderType = query["congestion_control"] ?? ""; + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; + } + + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var dicQuery = new Dictionary(); + if (item.Sni.IsNotEmpty()) + { + dicQuery.Add("sni", item.Sni); + } + if (item.Alpn.IsNotEmpty()) + { + dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); + } + dicQuery.Add("congestion_control", item.HeaderType); + + return ToUri(EConfigType.Juicity, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark); + } +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs new file mode 100644 index 00000000..95c5583d --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Fmt/NaiveFmt.cs @@ -0,0 +1,52 @@ +using static QRCoder.PayloadGenerator; + +namespace ServiceLib.Handler.Fmt; +public class NaiveFmt : BaseFmt +{ + public static ProfileItem? Resolve(string str, out string msg) + { + msg = ResUI.ConfigurationFormatIncorrect; + + var parsedUrl = Utils.TryUri(str); + if (parsedUrl == null) + { + return null; + } + + ProfileItem item = new() + { + ConfigType = EConfigType.NaiveProxy, + Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), + Address = parsedUrl.IdnHost, + Port = parsedUrl.Port, + }; + var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); + item.Id = rawUserInfo; + + var query = Utils.ParseQueryString(parsedUrl.Query); + _ = ResolveStdTransport(query, ref item); + + item.HeaderType = query["protocol"] ?? ""; + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; + } + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var pw = item.Id; + var dicQuery = new Dictionary(); + _ = GetStdTransport(item, Global.None, ref dicQuery); + dicQuery.Add("protocol", item.HeaderType); + + return ToUri(EConfigType.NaiveProxy, item.Address, item.Port, pw, dicQuery, remark); + } +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/ShadowquicFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/ShadowquicFmt.cs new file mode 100644 index 00000000..468923da --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Fmt/ShadowquicFmt.cs @@ -0,0 +1,63 @@ +namespace ServiceLib.Handler.Fmt; + +public class ShadowquicFmt : BaseFmt +{ + public static ProfileItem? Resolve(string str, out string msg) + { + msg = ResUI.ConfigurationFormatIncorrect; + + ProfileItem item = new() + { + ConfigType = EConfigType.Shadowquic + }; + + var url = Utils.TryUri(str); + if (url == null) + { + return null; + } + + item.Address = url.IdnHost; + item.Port = url.Port; + item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + var rawUserInfo = Utils.UrlDecode(url.UserInfo); + var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2); + if (userInfoParts.Length == 2) + { + item.Id = userInfoParts.First(); + item.Security = userInfoParts.Last(); + } + + var query = Utils.ParseQueryString(url.Query); + ResolveStdTransport(query, ref item); + item.HeaderType = query["congestion_control"] ?? ""; + + return item; + } + + public static string? ToUri(ProfileItem? item) + { + if (item == null) + { + return null; + } + + var remark = string.Empty; + if (item.Remarks.IsNotEmpty()) + { + remark = "#" + Utils.UrlEncode(item.Remarks); + } + var dicQuery = new Dictionary(); + if (item.Sni.IsNotEmpty()) + { + dicQuery.Add("sni", item.Sni); + } + if (item.Alpn.IsNotEmpty()) + { + dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn)); + } + dicQuery.Add("congestion_control", item.HeaderType); + + return ToUri(EConfigType.Shadowquic, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark); + } +} From ade4cbcf8002726e070464173438e1f088ebc001 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Mon, 28 Jul 2025 23:48:01 +0800 Subject: [PATCH 16/24] Adds Xray and Singbox config type support --- v2rayN/ServiceLib/Global.cs | 25 ++++++++++++ .../ServiceLib/Handler/CoreConfigHandler.cs | 8 +++- .../CoreConfig/CoreConfigSingboxService.cs | 13 ++++--- .../CoreConfig/CoreConfigV2rayService.cs | 39 +++++-------------- .../ServiceLib/Services/SpeedtestService.cs | 13 ++++--- 5 files changed, 55 insertions(+), 43 deletions(-) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index e7a2cd27..f7371cf2 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -330,6 +330,31 @@ public class Global EConfigType.SOCKS, ]; + public static readonly HashSet XraySupportConfigType = + [ + EConfigType.VMess, + EConfigType.VLESS, + EConfigType.Shadowsocks, + EConfigType.Trojan, + EConfigType.WireGuard, + EConfigType.SOCKS, + EConfigType.HTTP, + ]; + + public static readonly HashSet SingboxSupportConfigType = + [ + EConfigType.VMess, + EConfigType.VLESS, + EConfigType.Shadowsocks, + EConfigType.Trojan, + EConfigType.Hysteria2, + EConfigType.TUIC, + EConfigType.Anytls, + EConfigType.WireGuard, + EConfigType.SOCKS, + EConfigType.HTTP, + ]; + public static readonly List DomainStrategies = [ "AsIs", diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index 0ee2e227..f2d38bfc 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -27,10 +27,16 @@ public class CoreConfigHandler { result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node); } - else + else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.Xray) { result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node); } + else + { + result.Msg = ResUI.OperationFailed; + result.Success = false; + return result; + } if (result.Success != true) { return result; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index ea5d787d..5ff9c35d 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Reactive; @@ -142,7 +143,7 @@ public class CoreConfigSingboxService foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) { continue; } @@ -378,7 +379,7 @@ public class CoreConfigSingboxService var proxyProfiles = new List(); foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) { continue; } @@ -1117,7 +1118,7 @@ public class CoreConfigSingboxService var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); string? prevOutboundTag = null; if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom) + && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)) { prevOutboundTag = $"prev-{Global.ProxyTag}"; var prevServer = await GenServer(prevNode); @@ -1208,7 +1209,7 @@ public class CoreConfigSingboxService { var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom) + && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -1315,7 +1316,7 @@ public class CoreConfigSingboxService // Next proxy var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom) + && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)) { nextOutbound ??= await GenServer(nextNode); nextOutbound.tag = outbound.tag; @@ -1652,7 +1653,7 @@ public class CoreConfigSingboxService var node = await AppHandler.Instance.GetProfileItemViaRemarks(outboundTag); if (node == null - || node.ConfigType == EConfigType.Custom) + || !Global.SingboxSupportConfigType.Contains(node.ConfigType)) { return Global.ProxyTag; } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index 966e23f5..0920cb95 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Text.Json.Nodes; @@ -116,11 +117,7 @@ public class CoreConfigV2rayService var proxyProfiles = new List(); foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) - { - continue; - } - if (it.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) + if (!Global.XraySupportConfigType.Contains(it.ConfigType)) { continue; } @@ -255,7 +252,7 @@ public class CoreConfigV2rayService foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) { continue; } @@ -707,10 +704,7 @@ public class CoreConfigV2rayService var node = await AppHandler.Instance.GetProfileItemViaRemarks(outboundTag); if (node == null - || node.ConfigType == EConfigType.Custom - || node.ConfigType == EConfigType.Hysteria2 - || node.ConfigType == EConfigType.TUIC - || node.ConfigType == EConfigType.Anytls) + || !Global.SingboxSupportConfigType.Contains(node.ConfigType)) { return Global.ProxyTag; } @@ -1289,10 +1283,7 @@ public class CoreConfigV2rayService // Previous proxy var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom - && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC - && prevNode.ConfigType != EConfigType.Anytls + && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType) && Utils.IsDomain(prevNode.Address)) { domainList.Add(prevNode.Address); @@ -1301,10 +1292,7 @@ public class CoreConfigV2rayService // Next proxy var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom - && nextNode.ConfigType != EConfigType.Hysteria2 - && nextNode.ConfigType != EConfigType.TUIC - && nextNode.ConfigType != EConfigType.Anytls + && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType) && Utils.IsDomain(nextNode.Address)) { domainList.Add(nextNode.Address); @@ -1420,10 +1408,7 @@ public class CoreConfigV2rayService var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); string? prevOutboundTag = null; if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom - && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC - && prevNode.ConfigType != EConfigType.Anytls) + && Global.XraySupportConfigType.Contains(prevNode.ConfigType)) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -1496,10 +1481,7 @@ public class CoreConfigV2rayService { var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); if (prevNode is not null - && prevNode.ConfigType != EConfigType.Custom - && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC - && prevNode.ConfigType != EConfigType.Anytls) + && !Global.XraySupportConfigType.Contains(prevNode.ConfigType)) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -1566,10 +1548,7 @@ public class CoreConfigV2rayService // Next proxy var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom - && nextNode.ConfigType != EConfigType.Hysteria2 - && nextNode.ConfigType != EConfigType.TUIC - && nextNode.ConfigType != EConfigType.Anytls) + && !Global.XraySupportConfigType.Contains(nextNode.ConfigType)) { if (nextOutbound == null) { diff --git a/v2rayN/ServiceLib/Services/SpeedtestService.cs b/v2rayN/ServiceLib/Services/SpeedtestService.cs index 9c97a217..458cc334 100644 --- a/v2rayN/ServiceLib/Services/SpeedtestService.cs +++ b/v2rayN/ServiceLib/Services/SpeedtestService.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Diagnostics; +using System.Linq; using System.Net; using System.Net.Sockets; @@ -70,7 +71,7 @@ public class SpeedtestService var lstSelected = new List(); foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType))) { continue; } @@ -122,7 +123,7 @@ public class SpeedtestService List tasks = []; foreach (var it in selecteds) { - if (it.ConfigType == EConfigType.Custom) + if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType))) { continue; } @@ -207,7 +208,7 @@ public class SpeedtestService { continue; } - if (it.ConfigType == EConfigType.Custom) + if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType))) { continue; } @@ -244,7 +245,7 @@ public class SpeedtestService UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip); continue; } - if (it.ConfigType == EConfigType.Custom) + if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType))) { continue; } @@ -358,8 +359,8 @@ public class SpeedtestService private List> GetTestBatchItem(List lstSelected, int pageSize) { List> lstTest = new(); - var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)).ToList(); - var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls).ToList(); + var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList(); + var lst2 = lstSelected.Where(t => Global.SingboxSupportConfigType.Contains(t.ConfigType) && !Global.XraySupportConfigType.Contains(t.ConfigType)).ToList(); for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++) { From 46688d04b38546d03b9354b4b9cfb439af3933b8 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Tue, 29 Jul 2025 09:13:15 +0800 Subject: [PATCH 17/24] Fixes hy2 --- .../Minimal/CoreConfigHy2Service.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs index 3241aecd..819a3e55 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs @@ -60,6 +60,27 @@ public class CoreConfigHy2Service configJsonNode["server"] = node.Address + ":" + port; 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) { From bf73e0c8582af2cbe3eaf2e57003be0213271250 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Tue, 29 Jul 2025 09:24:32 +0800 Subject: [PATCH 18/24] Fixes --- v2rayN/ServiceLib/Handler/AppHandler.cs | 2 +- v2rayN/ServiceLib/Handler/CoreConfigHandler.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/AppHandler.cs b/v2rayN/ServiceLib/Handler/AppHandler.cs index 1422ad73..699162a5 100644 --- a/v2rayN/ServiceLib/Handler/AppHandler.cs +++ b/v2rayN/ServiceLib/Handler/AppHandler.cs @@ -256,7 +256,7 @@ public sealed class AppHandler var coreType = GetCoreType(profileItem, profileItem.ConfigType); ECoreType? preCoreType = null; - var pureEndpointCore = GetSplitCoreType(profileItem, profileItem.ConfigType); + var pureEndpointCore = profileItem.CoreType ?? GetSplitCoreType(profileItem, profileItem.ConfigType); var splitRouteCore = _config.SplitCoreItem.RouteCoreType; var enableTun = _config.TunModeItem.EnableTun; diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index f2d38bfc..41005efe 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -23,11 +23,11 @@ public class CoreConfigHandler _ => await GenerateClientCustomConfig(node, fileName) }; } - else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) + else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box || (Global.SingboxSupportConfigType.Contains(node.ConfigType) && !Global.XraySupportConfigType.Contains(node.ConfigType))) { result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node); } - else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.Xray) + else if (Global.XraySupportConfigType.Contains(node.ConfigType)) { result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node); } @@ -159,7 +159,7 @@ public class CoreConfigHandler { result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port); } - else + else if (Global.XraySupportConfigType.Contains(node.ConfigType)) { result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port); } @@ -179,7 +179,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); } From 631968459257af331982c1061498d954ac457694 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Wed, 30 Jul 2025 09:08:59 +0800 Subject: [PATCH 19/24] Enhances core configuration handling. --- v2rayN/ServiceLib/Common/Utils.cs | 21 +++++++++++++++++-- v2rayN/ServiceLib/Global.cs | 4 ++-- v2rayN/ServiceLib/Handler/CoreAdminHandler.cs | 2 +- v2rayN/ServiceLib/Handler/CoreHandler.cs | 20 +++++++++--------- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/v2rayN/ServiceLib/Common/Utils.cs b/v2rayN/ServiceLib/Common/Utils.cs index 38869ebb..f7fd3445 100644 --- a/v2rayN/ServiceLib/Common/Utils.cs +++ b/v2rayN/ServiceLib/Common/Utils.cs @@ -813,7 +813,7 @@ public class Utils } } - public static string GetBinConfigPath(string filename = "") + public static string GetBinConfigPath(string filename = "", ECoreType coreType = ECoreType.v2rayN) { var tempPath = Path.Combine(StartupPath(), "binConfigs"); if (!Directory.Exists(tempPath)) @@ -827,10 +827,27 @@ public class Utils } else { - return Path.Combine(tempPath, filename); + return Path.Combine(tempPath, GetBinConfigFileName(filename, coreType)); } } + public static string GetBinConfigFileName(string filename, ECoreType coreType = ECoreType.v2rayN) + { + var fileSuffix = coreType switch + { + ECoreType.sing_box => ".json", + ECoreType.Xray => ".json", + ECoreType.hysteria2 => ".json", + ECoreType.naiveproxy => ".json", + ECoreType.tuic => ".json", + ECoreType.juicity => ".json", + ECoreType.brook => ".cac", + ECoreType.shadowquic => ".yaml", + _ => string.Empty + }; + return filename.EndsWith(fileSuffix) ? filename : $"{filename}{fileSuffix}"; + } + #endregion TempPath #region Platform diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index f7371cf2..773f0bc9 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -12,8 +12,8 @@ public class Global public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="; public const string ConfigFileName = "guiNConfig.json"; - public const string CoreConfigFileName = "config.json"; - public const string CorePreConfigFileName = "configPre.json"; + public const string CoreConfigFileName = "config"; + public const string CorePreConfigFileName = "configPre"; public const string CoreSpeedtestConfigFileName = "configTest{0}.json"; public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json"; public const string ClashMixinConfigFileName = "Mixin.yaml"; diff --git a/v2rayN/ServiceLib/Handler/CoreAdminHandler.cs b/v2rayN/ServiceLib/Handler/CoreAdminHandler.cs index ec99b26b..9ce5a411 100644 --- a/v2rayN/ServiceLib/Handler/CoreAdminHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreAdminHandler.cs @@ -35,7 +35,7 @@ public class CoreAdminHandler { StringBuilder sb = new(); sb.AppendLine("#!/bin/bash"); - var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}"; + var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath, coreInfo.CoreType).AppendQuotes())}"; sb.AppendLine($"sudo -S {cmdLine}"); var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true); diff --git a/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayN/ServiceLib/Handler/CoreHandler.cs index 201b9ba6..3ab29804 100644 --- a/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -101,7 +101,7 @@ public class CoreHandler { var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) ? ECoreType.sing_box : ECoreType.Xray; var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); - var configPath = Utils.GetBinConfigPath(fileName); + var configPath = Utils.GetBinConfigPath(fileName, coreType); var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType); UpdateFunc(false, result.Msg); if (result.Success != true) @@ -130,15 +130,15 @@ public class CoreHandler return -1; } + var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); 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, node, 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) @@ -231,7 +231,8 @@ public class CoreHandler private async Task CoreStart(CoreLaunchContext context) { - var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); + var coreType = context.SplitCore ? context.PureEndpointCore : context.CoreType; + var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName, coreType); var result = context.SplitCore ? await CoreConfigHandler.GeneratePureEndpointConfig(context.Node, fileName) : await CoreConfigHandler.GenerateClientConfig(context.Node, fileName); @@ -257,7 +258,7 @@ public class CoreHandler var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(context.CoreType); var displayLog = context.Node.ConfigType != EConfigType.Custom || context.Node.DisplayLog; - var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true); + var proc = await RunProcess(coreInfo, Utils.GetBinConfigFileName(Global.CoreConfigFileName, coreType), displayLog, true); if (proc is null) { @@ -266,8 +267,7 @@ public class CoreHandler } _process = proc; - var (_, coreType, preCoreType) = AppHandler.Instance.GetCoreAndPreType(context.Node); - _config.RunningCoreType = (ECoreType)(preCoreType != null ? preCoreType : coreType); + _config.RunningCoreType = (ECoreType)(context.PreCoreType != null ? context.PreCoreType : coreType); return true; } @@ -278,7 +278,7 @@ public class CoreHandler return true; // No pre-core needed, consider successful } - var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); + var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName, (ECoreType)context.PreCoreType); var itemSocks = new ProfileItem() { CoreType = context.PreCoreType, @@ -296,7 +296,7 @@ public class CoreHandler } var coreInfo = CoreInfoHandler.Instance.GetCoreInfo((ECoreType)context.PreCoreType); - var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true); + var proc = await RunProcess(coreInfo, Utils.GetBinConfigFileName(Global.CorePreConfigFileName, (ECoreType)context.PreCoreType), true, true); if (proc is null || (_process?.HasExited == true)) { @@ -355,7 +355,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, From c820b580677cc2937ab7bd8df010363f56014e34 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Wed, 30 Jul 2025 09:20:18 +0800 Subject: [PATCH 20/24] Fixes Shadowquic Config Generate --- .../Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs index 232b56df..c16c99f3 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs @@ -53,7 +53,7 @@ public class CoreConfigShadowquicService configYamlNode["inbound"] = inboundNode; // outbound - var alpn = new JsonArray(); + var alpn = new List(); foreach (var item in node.GetAlpn() ?? new List()) { alpn.Add(item); From 271fdbc2a8797645c2914eb4ed49ab830cd66a95 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 31 Jul 2025 10:46:01 +0800 Subject: [PATCH 21/24] Refactor --- .../ServiceLib/Handler/CoreConfigHandler.cs | 228 ++++++++++-------- v2rayN/ServiceLib/Handler/CoreHandler.cs | 65 +---- v2rayN/ServiceLib/Models/CoreLaunchContext.cs | 53 ++++ .../CoreConfig/CoreConfigServiceBase.cs | 83 +++++++ .../CoreConfig/CoreConfigSingboxService.cs | 24 +- .../CoreConfig/CoreConfigV2rayService.cs | 37 ++- .../Minimal/CoreConfigBrookService.cs | 14 +- .../{ => Minimal}/CoreConfigClashService.cs | 18 +- .../Minimal/CoreConfigHy2Service.cs | 22 +- .../Minimal/CoreConfigJuicityService.cs | 14 +- .../Minimal/CoreConfigNaiveService.cs | 14 +- .../Minimal/CoreConfigShadowquicService.cs | 14 +- .../Minimal/CoreConfigTuicService.cs | 14 +- .../ViewModels/ProfilesViewModel.cs | 6 +- 14 files changed, 337 insertions(+), 269 deletions(-) create mode 100644 v2rayN/ServiceLib/Models/CoreLaunchContext.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/CoreConfigServiceBase.cs rename v2rayN/ServiceLib/Services/CoreConfig/{ => Minimal}/CoreConfigClashService.cs (93%) diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index 41005efe..780e6f2b 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -9,33 +9,26 @@ public class CoreConfigHandler { private static readonly string _tag = "CoreConfigHandler"; - public static async Task GenerateClientConfig(ProfileItem node, string? fileName) + public static async Task GenerateClientConfig(CoreLaunchContext context, string? fileName) { - var config = AppHandler.Instance.Config; var result = new RetResult(); - if (node.ConfigType == EConfigType.Custom) + if (context.ConfigType == EConfigType.Custom) { - result = node.CoreType switch - { - ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName), - ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName), - _ => await GenerateClientCustomConfig(node, fileName) - }; - } - else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box || (Global.SingboxSupportConfigType.Contains(node.ConfigType) && !Global.XraySupportConfigType.Contains(node.ConfigType))) - { - result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node); - } - else if (Global.XraySupportConfigType.Contains(node.ConfigType)) - { - result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node); + result = await GetCoreConfigServiceForCustom(context.CoreType).GenerateClientCustomConfig(context.Node, fileName); } else { - result.Msg = ResUI.OperationFailed; - result.Success = false; - return result; + 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) { @@ -49,96 +42,44 @@ public class CoreConfigHandler return result; } - public static async Task GeneratePureEndpointConfig(ProfileItem node, string? fileName) + public static async Task GeneratePassthroughConfig(CoreLaunchContext context, string? fileName) { - var config = AppHandler.Instance.Config; var result = new RetResult(); - var coreType = AppHandler.Instance.GetSplitCoreType(node, node.ConfigType); - - result = coreType switch - { - ECoreType.sing_box => await new CoreConfigSingboxService(config).GeneratePureEndpointConfig(node), - ECoreType.Xray => await new CoreConfigV2rayService(config).GeneratePureEndpointConfig(node), - ECoreType.hysteria2 => await new CoreConfigHy2Service(config).GeneratePureEndpointConfig(node), - ECoreType.naiveproxy => await new CoreConfigNaiveService(config).GeneratePureEndpointConfig(node), - ECoreType.tuic => await new CoreConfigTuicService(config).GeneratePureEndpointConfig(node), - ECoreType.juicity => await new CoreConfigJuicityService(config).GeneratePureEndpointConfig(node), - ECoreType.brook => await new CoreConfigBrookService(config).GeneratePureEndpointConfig(node), - ECoreType.shadowquic => await new CoreConfigShadowquicService(config).GeneratePureEndpointConfig(node), - _ => throw new NotImplementedException(), - }; - - if (result.Success != true) - { - return result; - } - if (fileName.IsNotEmpty() && result.Data != null) - { - await File.WriteAllTextAsync(fileName, result.Data.ToString()); - } - return result; - } - - private static async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) - { - var ret = new RetResult(); try { - if (node == null || fileName is null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - if (File.Exists(fileName)) - { - File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail - File.Delete(fileName); - } - - string addressFileName = node.Address; - if (!File.Exists(addressFileName)) - { - addressFileName = Utils.GetConfigPath(addressFileName); - } - if (!File.Exists(addressFileName)) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - File.Copy(addressFileName, fileName); - File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file. - - //check again - if (!File.Exists(fileName)) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); - ret.Success = true; - return await Task.FromResult(ret); + result = await GetCoreConfigServiceForPassthrough(context.CoreType).GeneratePassthroughConfig(context.Node); } catch (Exception ex) { Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; + result.Msg = ResUI.FailedGenDefaultConfiguration; + return result; } + + if (result.Success != true) + { + return result; + } + if (fileName.IsNotEmpty() && result.Data != null) + { + await File.WriteAllTextAsync(fileName, result.Data.ToString()); + } + return result; } public static async Task GenerateClientSpeedtestConfig(Config config, string fileName, List selecteds, ECoreType coreType) { var result = new RetResult(); - if (coreType == ECoreType.sing_box) + try { - result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds); + result = await GetCoreConfigServiceForMultipleSpeedtest(coreType).GenerateClientSpeedtestConfig(selecteds); } - else if (coreType == ECoreType.Xray) + catch (Exception ex) { - result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds); + Logging.SaveLog(_tag, ex); + result.Msg = ResUI.FailedGenDefaultConfiguration; + return result; } if (result.Success != true) { @@ -148,21 +89,24 @@ public class CoreConfigHandler return result; } - public static async Task GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName) + public static async Task GenerateClientSpeedtestConfig(Config config, CoreLaunchContext context, ServerTestItem testItem, string fileName) { var result = new RetResult(); var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest); var port = Utils.GetFreePort(initPort + testItem.QueueNum); testItem.Port = port; - if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) + try { - result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port); + result = await GetCoreConfigServiceForSpeedtest(context.CoreType).GenerateClientSpeedtestConfig(context.Node, port); } - else if (Global.XraySupportConfigType.Contains(node.ConfigType)) + 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; @@ -191,4 +135,94 @@ 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); + default: + // CoreConfigServiceMinimalBase + return new CoreConfigV2rayService(AppHandler.Instance.Config); + } + } + + private static CoreConfigServiceBase GetCoreConfigServiceForClientConfig(ECoreType coreType) + { + switch (coreType) + { + case ECoreType.sing_box: + return new CoreConfigSingboxService(AppHandler.Instance.Config); + case ECoreType.Xray: + return new CoreConfigV2rayService(AppHandler.Instance.Config); + default: + throw new NotImplementedException($"Core type {coreType} is not implemented for client configuration."); + } + } } diff --git a/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayN/ServiceLib/Handler/CoreHandler.cs index 3ab29804..01838cc1 100644 --- a/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -130,10 +130,12 @@ public class CoreHandler return -1; } - var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); + 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, coreType); - var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath); + var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, context, testItem, configPath); if (result.Success != true) { return -1; @@ -179,63 +181,13 @@ public class CoreHandler #region Private - /// - /// Core launch context that encapsulates all parameters required for launching - /// - private 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 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; - } - - /// - /// Adjust context parameters based on configuration type - /// - public void AdjustForConfigType() - { - (SplitCore, CoreType, PreCoreType) = AppHandler.Instance.GetCoreAndPreType(Node); - if (Node.ConfigType == EConfigType.Custom) - { - if (Node.PreSocksPort > 0) - { - PreSocksPort = Node.PreSocksPort.Value; - } - else - { - EnableTun = false; - } - } - else if (PreCoreType != null) - { - PreSocksPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.split); - } - } - } - private async Task CoreStart(CoreLaunchContext context) { var coreType = context.SplitCore ? context.PureEndpointCore : context.CoreType; var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName, coreType); var result = context.SplitCore - ? await CoreConfigHandler.GeneratePureEndpointConfig(context.Node, fileName) - : await CoreConfigHandler.GenerateClientConfig(context.Node, fileName); + ? await CoreConfigHandler.GeneratePassthroughConfig(context, fileName) + : await CoreConfigHandler.GenerateClientConfig(context, fileName); if (result.Success != true) { @@ -287,8 +239,9 @@ public class CoreHandler Sni = context.EnableTun && Utils.IsDomain(context.Node.Address) ? context.Node.Address : string.Empty, //Tun2SocksAddress Port = context.PreSocksPort }; - - var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName); + var itemSocksLaunch = new CoreLaunchContext(itemSocks, _config); + + var result = await CoreConfigHandler.GenerateClientConfig(itemSocksLaunch, fileName); if (!result.Success) { UpdateFunc(true, result.Msg); diff --git a/v2rayN/ServiceLib/Models/CoreLaunchContext.cs b/v2rayN/ServiceLib/Models/CoreLaunchContext.cs new file mode 100644 index 00000000..cf327dac --- /dev/null +++ b/v2rayN/ServiceLib/Models/CoreLaunchContext.cs @@ -0,0 +1,53 @@ +namespace ServiceLib.Models; + +/// +/// Core launch context that encapsulates all parameters required for launching +/// +public class CoreLaunchContext +{ + public ProfileItem Node { get; set; } + public bool SplitCore { get; set; } + public ECoreType CoreType { get; set; } + public ECoreType? PreCoreType { get; set; } + public ECoreType PureEndpointCore { get; set; } + public ECoreType SplitRouteCore { get; set; } + public bool EnableTun { get; set; } + public int PreSocksPort { get; set; } + public EConfigType ConfigType { get; set; } + + public CoreLaunchContext(ProfileItem node, Config config) + { + Node = node; + SplitCore = config.SplitCoreItem.EnableSplitCore; + CoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType); + PureEndpointCore = AppHandler.Instance.GetSplitCoreType(node, node.ConfigType); + SplitRouteCore = config.SplitCoreItem.RouteCoreType; + EnableTun = config.TunModeItem.EnableTun; + PreSocksPort = 0; + PreCoreType = null; + ConfigType = node.ConfigType; + } + + /// + /// Adjust context parameters based on configuration type + /// + public void AdjustForConfigType() + { + (SplitCore, CoreType, PreCoreType) = AppHandler.Instance.GetCoreAndPreType(Node); + if (Node.ConfigType == EConfigType.Custom) + { + if (Node.PreSocksPort > 0) + { + PreSocksPort = Node.PreSocksPort.Value; + } + else + { + EnableTun = false; + } + } + else if (PreCoreType != null) + { + PreSocksPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.split); + } + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigServiceBase.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigServiceBase.cs new file mode 100644 index 00000000..9015fbe6 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigServiceBase.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using YamlDotNet.Core.Tokens; + +namespace ServiceLib.Services.CoreConfig; + +public abstract class CoreConfigServiceMinimalBase(Config config) +{ + public virtual string _tag => GetType().Name; + protected Config _config = config; + + public virtual Task GeneratePassthroughConfig(ProfileItem node) + { + return GeneratePassthroughConfig(node, AppHandler.Instance.GetLocalPort(EInboundProtocol.split)); + } + public virtual Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + { + return GeneratePassthroughConfig(node, port); + } + protected abstract Task GeneratePassthroughConfig(ProfileItem node, int port); + public virtual async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + { + var ret = new RetResult(); + try + { + if (node == null || fileName is null) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (File.Exists(fileName)) + { + File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail + File.Delete(fileName); + } + + string addressFileName = node.Address; + if (!File.Exists(addressFileName)) + { + addressFileName = Utils.GetConfigPath(addressFileName); + } + if (!File.Exists(addressFileName)) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + File.Copy(addressFileName, fileName); + File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file. + + //check again + if (!File.Exists(fileName)) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } +} + +public abstract class CoreConfigServiceBase(Config config) : CoreConfigServiceMinimalBase(config) +{ + public abstract Task GenerateClientConfigContent(ProfileItem node); + public abstract Task GenerateClientMultipleLoadConfig(List selecteds, EMultipleLoad multipleLoad); + public virtual Task GenerateClientMultipleLoadConfig(List selecteds) + { + return GenerateClientMultipleLoadConfig(selecteds, EMultipleLoad.LeastPing); + } + public abstract Task GenerateClientSpeedtestConfig(List selecteds); +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 5ff9c35d..51678637 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -9,19 +9,11 @@ using ServiceLib.Models; namespace ServiceLib.Services.CoreConfig; -public class CoreConfigSingboxService +public class CoreConfigSingboxService(Config config) : CoreConfigServiceBase(config) { - private Config _config; - private static readonly string _tag = "CoreConfigSingboxService"; - - public CoreConfigSingboxService(Config config) - { - _config = config; - } - #region public gen function - public async Task GenerateClientConfigContent(ProfileItem node) + public override async Task GenerateClientConfigContent(ProfileItem node) { var ret = new RetResult(); try @@ -94,7 +86,7 @@ public class CoreConfigSingboxService } } - public async Task GenerateClientSpeedtestConfig(List selecteds) + public override async Task GenerateClientSpeedtestConfig(List selecteds) { var ret = new RetResult(); try @@ -271,7 +263,7 @@ public class CoreConfigSingboxService } } - public async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + public override async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) { var ret = new RetResult(); try @@ -342,7 +334,7 @@ public class CoreConfigSingboxService } } - public async Task GenerateClientMultipleLoadConfig(List selecteds) + public override async Task GenerateClientMultipleLoadConfig(List selecteds, EMultipleLoad multipleLoad) { var ret = new RetResult(); try @@ -434,7 +426,7 @@ public class CoreConfigSingboxService } } - public async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + public override async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) { var ret = new RetResult(); if (node == null || fileName is null) @@ -515,7 +507,7 @@ public class CoreConfigSingboxService } } - public async Task GeneratePureEndpointConfig(ProfileItem node) + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) { var ret = new RetResult(); try @@ -555,7 +547,7 @@ public class CoreConfigSingboxService type = EInboundProtocol.mixed.ToString(), tag = EInboundProtocol.socks.ToString(), listen = Global.Loopback, - listen_port = AppHandler.Instance.GetLocalPort(EInboundProtocol.split) + listen_port = port }; singboxConfig.inbounds = new() { inbound }; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index 0920cb95..7f219032 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -5,19 +5,11 @@ using System.Text.Json.Nodes; namespace ServiceLib.Services.CoreConfig; -public class CoreConfigV2rayService +public class CoreConfigV2rayService(Config config) : CoreConfigServiceBase(config) { - private Config _config; - private static readonly string _tag = "CoreConfigV2rayService"; - - public CoreConfigV2rayService(Config config) - { - _config = config; - } - #region public gen function - public async Task GenerateClientConfigContent(ProfileItem node) + public override async Task GenerateClientConfigContent(ProfileItem node) { var ret = new RetResult(); try @@ -78,7 +70,7 @@ public class CoreConfigV2rayService } } - public async Task GenerateClientMultipleLoadConfig(List selecteds, EMultipleLoad multipleLoad) + public override async Task GenerateClientMultipleLoadConfig(List selecteds, EMultipleLoad multipleLoad) { var ret = new RetResult(); @@ -203,7 +195,7 @@ public class CoreConfigV2rayService } } - public async Task GenerateClientSpeedtestConfig(List selecteds) + public override async Task GenerateClientSpeedtestConfig(List selecteds) { var ret = new RetResult(); try @@ -355,7 +347,7 @@ public class CoreConfigV2rayService } } - public async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + public override async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) { var ret = new RetResult(); try @@ -413,7 +405,7 @@ public class CoreConfigV2rayService } } - public async Task GeneratePureEndpointConfig(ProfileItem node) + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) { var ret = new RetResult(); try @@ -449,13 +441,18 @@ public class CoreConfigV2rayService await GenLog(v2rayConfig); - var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.split, true); - inbound.sniffing = new Sniffing4Ray + v2rayConfig.inbounds = new() { new() { - enabled = false - }; - - v2rayConfig.inbounds = new() { inbound }; + 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()); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs index 6ef1353e..b5f34dbe 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigBrookService.cs @@ -1,15 +1,7 @@ namespace ServiceLib.Services.CoreConfig.Minimal; -public class CoreConfigBrookService +public class CoreConfigBrookService(Config config) : CoreConfigServiceMinimalBase(config) { - private Config _config; - private static readonly string _tag = "CoreConfigBrookService"; - - public CoreConfigBrookService(Config config) - { - _config = config; - } - - public async Task GeneratePureEndpointConfig(ProfileItem node) + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) { var ret = new RetResult(); try @@ -30,7 +22,7 @@ public class CoreConfigBrookService var processArgs = "client"; // inbound - processArgs += " --socks5 " + Global.Loopback + ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).ToString(); + processArgs += " --socks5 " + Global.Loopback + ":" + port.ToString(); // outbound processArgs += " --server " + node.Address + ":" + node.Port; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigClashService.cs similarity index 93% rename from v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs rename to v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigClashService.cs index 50c6df67..313dabf0 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigClashService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigClashService.cs @@ -1,19 +1,21 @@ -namespace ServiceLib.Services.CoreConfig; +namespace ServiceLib.Services.CoreConfig.Minimal; /// /// Core configuration file processing class /// -public class CoreConfigClashService +public class CoreConfigClashService(Config config) : CoreConfigServiceMinimalBase(config) { - private Config _config; - private static readonly string _tag = "CoreConfigClashService"; - - public CoreConfigClashService(Config config) + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) { - _config = config; + var ret = new RetResult + { + Success = false, + Msg = ResUI.OperationFailed + }; + return await Task.FromResult(ret); } - public async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + public override async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) { var ret = new RetResult(); if (node == null || fileName is null) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs index 819a3e55..fe269a5b 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs @@ -1,17 +1,9 @@ using System.Text.Json.Nodes; namespace ServiceLib.Services.CoreConfig.Minimal; -public class CoreConfigHy2Service +public class CoreConfigHy2Service(Config config) : CoreConfigServiceMinimalBase(config) { - private Config _config; - private static readonly string _tag = "CoreConfigHy2Service"; - - public CoreConfigHy2Service(Config config) - { - _config = config; - } - - public async Task GeneratePureEndpointConfig(ProfileItem node) + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) { var ret = new RetResult(); try @@ -34,14 +26,14 @@ public class CoreConfigHy2Service // inbound configJsonNode["socks5"] = new JsonObject { - ["listen"] = Global.Loopback + ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).ToString() + ["listen"] = Global.Loopback + ":" + port.ToString() }; // outbound - var port = string.Empty; + var outboundPort = string.Empty; if (node.Ports.IsNotEmpty()) { - port = node.Ports.Replace(':', '-'); + outboundPort = node.Ports.Replace(':', '-'); if (_config.HysteriaItem.HopInterval > 0) { configJsonNode["transport"] = new JsonObject @@ -55,9 +47,9 @@ public class CoreConfigHy2Service } else { - port = node.Port.ToString(); + outboundPort = node.Port.ToString(); } - configJsonNode["server"] = node.Address + ":" + port; + configJsonNode["server"] = node.Address + ":" + outboundPort; configJsonNode["auth"] = node.Id; if (node.Sni.IsNotEmpty()) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs index 66fccbbd..0d73147d 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigJuicityService.cs @@ -1,17 +1,9 @@ using System.Text.Json.Nodes; namespace ServiceLib.Services.CoreConfig.Minimal; -public class CoreConfigJuicityService +public class CoreConfigJuicityService(Config config) : CoreConfigServiceMinimalBase(config) { - private Config _config; - private static readonly string _tag = "CoreConfigJuicityService"; - - public CoreConfigJuicityService(Config config) - { - _config = config; - } - - public async Task GeneratePureEndpointConfig(ProfileItem node) + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) { var ret = new RetResult(); try @@ -45,7 +37,7 @@ public class CoreConfigJuicityService configJsonNode["log_level"] = logLevel; // inbound - configJsonNode["listen"] = ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).ToString(); + configJsonNode["listen"] = ":" + port.ToString(); // outbound configJsonNode["server"] = node.Address + ":" + node.Port; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs index 0e1230cc..b86e053a 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigNaiveService.cs @@ -1,17 +1,9 @@ using System.Text.Json.Nodes; namespace ServiceLib.Services.CoreConfig.Minimal; -public class CoreConfigNaiveService +public class CoreConfigNaiveService(Config config) : CoreConfigServiceMinimalBase(config) { - private Config _config; - private static readonly string _tag = "CoreConfigNaiveService"; - - public CoreConfigNaiveService(Config config) - { - _config = config; - } - - public async Task GeneratePureEndpointConfig(ProfileItem node) + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) { var ret = new RetResult(); try @@ -32,7 +24,7 @@ public class CoreConfigNaiveService var configJsonNode = new JsonObject(); // inbound - configJsonNode["listen"] = Global.SocksProtocol + Global.Loopback + ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).ToString(); + configJsonNode["listen"] = Global.SocksProtocol + Global.Loopback + ":" + port.ToString(); // outbound configJsonNode["proxy"] = (node.HeaderType == "quic" ? "quic://" : Global.HttpsProtocol) + node.Id + "@" + node.Address + ":" + node.Port; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs index c16c99f3..86ec9c53 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigShadowquicService.cs @@ -1,17 +1,9 @@ using System.Text.Json.Nodes; namespace ServiceLib.Services.CoreConfig.Minimal; -public class CoreConfigShadowquicService +public class CoreConfigShadowquicService(Config config) : CoreConfigServiceMinimalBase(config) { - private Config _config; - private static readonly string _tag = "CoreConfigShadowquicService"; - - public CoreConfigShadowquicService(Config config) - { - _config = config; - } - - public async Task GeneratePureEndpointConfig(ProfileItem node) + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) { var ret = new RetResult(); try @@ -48,7 +40,7 @@ public class CoreConfigShadowquicService var inboundNode = new Dictionary { ["type"] = "socks5", - ["listen"] = Global.Loopback + ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).ToString() + ["listen"] = Global.Loopback + ":" + port.ToString() }; configYamlNode["inbound"] = inboundNode; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs index 55dfecb1..0edc612c 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigTuicService.cs @@ -1,17 +1,9 @@ using System.Text.Json.Nodes; namespace ServiceLib.Services.CoreConfig.Minimal; -public class CoreConfigTuicService +public class CoreConfigTuicService(Config config) : CoreConfigServiceMinimalBase(config) { - private Config _config; - private static readonly string _tag = "CoreConfigTuicService"; - - public CoreConfigTuicService(Config config) - { - _config = config; - } - - public async Task GeneratePureEndpointConfig(ProfileItem node) + protected override async Task GeneratePassthroughConfig(ProfileItem node, int port) { var ret = new RetResult(); try @@ -47,7 +39,7 @@ public class CoreConfigTuicService // inbound configJsonNode["local"] = new JsonObject { - ["server"] = Global.Loopback + ":" + AppHandler.Instance.GetLocalPort(EInboundProtocol.split).ToString() + ["server"] = Global.Loopback + ":" + port.ToString() }; // outbound diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index f312b890..3c25b073 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -770,7 +770,8 @@ public class ProfilesViewModel : MyReactiveObject } if (blClipboard) { - var result = await CoreConfigHandler.GenerateClientConfig(item, null); + var coreLaunchContext = new CoreLaunchContext(item, _config); + var result = await CoreConfigHandler.GenerateClientConfig(coreLaunchContext, null); if (result.Success != true) { NoticeHandler.Instance.Enqueue(result.Msg); @@ -793,7 +794,8 @@ public class ProfilesViewModel : MyReactiveObject { return; } - var result = await CoreConfigHandler.GenerateClientConfig(item, fileName); + var coreLaunchContext = new CoreLaunchContext(item, _config); + var result = await CoreConfigHandler.GenerateClientConfig(coreLaunchContext, fileName); if (result.Success != true) { NoticeHandler.Instance.Enqueue(result.Msg); From f737868b36ef481fe32f21c8dabbfd252d4ea673 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 31 Jul 2025 10:54:01 +0800 Subject: [PATCH 22/24] Fix missing hysteria2 arguments --- v2rayN/ServiceLib/Handler/CoreInfoHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs b/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs index 6b7e1df2..befd80fa 100644 --- a/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreInfoHandler.cs @@ -130,7 +130,7 @@ public sealed class CoreInfoHandler { CoreType = ECoreType.hysteria, CoreExes = ["hysteria"], - Arguments = "", + Arguments = "-c {0}", Url = GetCoreUrl(ECoreType.hysteria), }, @@ -180,7 +180,7 @@ public sealed class CoreInfoHandler { CoreType = ECoreType.hysteria2, CoreExes = ["hysteria-windows-amd64", "hysteria-linux-amd64", "hysteria"], - Arguments = "", + Arguments = "-c {0}", Url = GetCoreUrl(ECoreType.hysteria2), }, From 6905eb33f8945e8e18f7128f2e726659f08447e3 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Fri, 1 Aug 2025 11:29:29 +0800 Subject: [PATCH 23/24] Fix hy2 custom config --- .../ServiceLib/Handler/CoreConfigHandler.cs | 2 + .../Minimal/CoreConfigHy2Service.cs | 69 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index 780e6f2b..ae68ce80 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -207,6 +207,8 @@ public class CoreConfigHandler 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); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs index fe269a5b..9f899969 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Minimal/CoreConfigHy2Service.cs @@ -99,4 +99,73 @@ public class CoreConfigHy2Service(Config config) : CoreConfigServiceMinimalBase( return await Task.FromResult(ret); } } + + public override async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) + { + var ret = new RetResult(); + try + { + if (node == null || fileName is null) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (File.Exists(fileName)) + { + File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail + File.Delete(fileName); + } + + string addressFileName = node.Address; + if (!File.Exists(addressFileName)) + { + addressFileName = Utils.GetConfigPath(addressFileName); + } + if (!File.Exists(addressFileName)) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + // Try deserializing the file to check if it is a valid JSON or YAML file + var fileContent = File.ReadAllText(addressFileName); + var jsonContent = JsonUtils.Deserialize(fileContent); + if (jsonContent != null) + { + File.Copy(addressFileName, fileName); + } + else + { + // If it's YAML, convert to JSON and write it + var yamlContent = YamlUtils.FromYaml>(fileContent); + if (yamlContent != null) + { + File.WriteAllText(fileName, JsonUtils.Serialize(yamlContent, true)); + } + else + { + ret.Msg = ResUI.FailedReadConfiguration + "2"; + return ret; + } + } + File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file. + + //check again + if (!File.Exists(fileName)) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + return await Task.FromResult(ret); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } } From a898c575184773f9bbb5550d38af426fb3799d0a Mon Sep 17 00:00:00 2001 From: DHR60 Date: Fri, 1 Aug 2025 11:30:07 +0800 Subject: [PATCH 24/24] Fix custom config core type not working --- v2rayN/ServiceLib/Handler/AppHandler.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/v2rayN/ServiceLib/Handler/AppHandler.cs b/v2rayN/ServiceLib/Handler/AppHandler.cs index 699162a5..f8495a79 100644 --- a/v2rayN/ServiceLib/Handler/AppHandler.cs +++ b/v2rayN/ServiceLib/Handler/AppHandler.cs @@ -235,6 +235,11 @@ 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; } @@ -246,6 +251,11 @@ public sealed class AppHandler 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; } @@ -266,7 +276,7 @@ public sealed class AppHandler coreType = profileItem.CoreType ?? ECoreType.Xray; if (profileItem.PreSocksPort > 0) { - preCoreType = enableTun ? ECoreType.sing_box : GetCoreType(profileItem, profileItem.ConfigType); + preCoreType = enableTun ? ECoreType.sing_box : GetCoreType(profileItem.ConfigType); } else {