diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 486e53ed..e502435f 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -90,6 +90,8 @@ public class Global public const string SingboxFakeDNSTag = "fake_dns"; public const string SingboxEchDNSTag = "ech_dns"; + public const int Hysteria2DefaultHopInt = 10; + public static readonly List IEProxyProtocols = [ "{ip}:{http_port}", diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index f9ff4494..0ea515ec 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -230,12 +230,8 @@ public static class ConfigHandler item.Remarks = profileItem.Remarks; item.Address = profileItem.Address; item.Port = profileItem.Port; - item.Ports = profileItem.Ports; - item.Id = profileItem.Id; - item.AlterId = profileItem.AlterId; - item.Security = profileItem.Security; - item.Flow = profileItem.Flow; + item.Password = profileItem.Password; item.Network = profileItem.Network; item.HeaderType = profileItem.HeaderType; @@ -258,6 +254,7 @@ public static class ConfigHandler item.CertSha = profileItem.CertSha; item.EchConfigList = profileItem.EchConfigList; item.EchForceQuery = profileItem.EchForceQuery; + item.ProtoExtra = profileItem.ProtoExtra; } var ret = item.ConfigType switch @@ -290,19 +287,22 @@ public static class ConfigHandler profileItem.ConfigType = EConfigType.VMess; profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Password = profileItem.Password.TrimEx(); + profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with + { + VmessSecurity = profileItem.GetProtocolExtra().VmessSecurity?.TrimEx() + }); profileItem.Network = profileItem.Network.TrimEx(); profileItem.HeaderType = profileItem.HeaderType.TrimEx(); profileItem.RequestHost = profileItem.RequestHost.TrimEx(); profileItem.Path = profileItem.Path.TrimEx(); profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx(); - if (!Global.VmessSecurities.Contains(profileItem.Security)) + if (!Global.VmessSecurities.Contains(profileItem.GetProtocolExtra().VmessSecurity)) { return -1; } - if (profileItem.Id.IsNullOrEmpty()) + if (profileItem.Password.IsNullOrEmpty()) { return -1; } @@ -360,11 +360,6 @@ public static class ConfigHandler { } } - else if (profileItem.ConfigType.IsGroupType()) - { - var profileGroupItem = await AppManager.Instance.GetProfileGroupItem(it.IndexId); - await AddGroupServerCommon(config, profileItem, profileGroupItem, true); - } else { await AddServerCommon(config, profileItem, true); @@ -610,14 +605,17 @@ public static class ConfigHandler profileItem.ConfigType = EConfigType.Shadowsocks; profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Password = profileItem.Password.TrimEx(); + profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with + { + SsMethod = profileItem.GetProtocolExtra().SsMethod?.TrimEx() + }); - if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.Security)) + if (!AppManager.Instance.GetShadowsocksSecurities(profileItem).Contains(profileItem.GetProtocolExtra().SsMethod)) { return -1; } - if (profileItem.Id.IsNullOrEmpty()) + if (profileItem.Password.IsNullOrEmpty()) { return -1; } @@ -678,12 +676,12 @@ public static class ConfigHandler profileItem.ConfigType = EConfigType.Trojan; profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); + profileItem.Password = profileItem.Password.TrimEx(); if (profileItem.StreamSecurity.IsNullOrEmpty()) { profileItem.StreamSecurity = Global.StreamSecurity; } - if (profileItem.Id.IsNullOrEmpty()) + if (profileItem.Password.IsNullOrEmpty()) { return -1; } @@ -708,18 +706,24 @@ public static class ConfigHandler //profileItem.CoreType = ECoreType.sing_box; profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.Path = profileItem.Path.TrimEx(); + profileItem.Password = profileItem.Password.TrimEx(); profileItem.Network = string.Empty; if (profileItem.StreamSecurity.IsNullOrEmpty()) { profileItem.StreamSecurity = Global.StreamSecurity; } - if (profileItem.Id.IsNullOrEmpty()) + if (profileItem.Password.IsNullOrEmpty()) { return -1; } + profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with + { + SalamanderPass = profileItem.GetProtocolExtra().SalamanderPass?.TrimEx(), + UpMbps = profileItem.GetProtocolExtra().UpMbps is null or < 0 ? config.HysteriaItem.UpMbps : profileItem.GetProtocolExtra().UpMbps, + DownMbps = profileItem.GetProtocolExtra().DownMbps is null or < 0 ? config.HysteriaItem.DownMbps : profileItem.GetProtocolExtra().DownMbps, + HopInterval = profileItem.GetProtocolExtra().HopInterval?.TrimEx(), + }); await AddServerCommon(config, profileItem, toFile); @@ -741,8 +745,8 @@ public static class ConfigHandler profileItem.CoreType = ECoreType.sing_box; profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Username = profileItem.Username.TrimEx(); + profileItem.Password = profileItem.Password.TrimEx(); profileItem.Network = string.Empty; if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType)) @@ -758,7 +762,7 @@ public static class ConfigHandler { profileItem.Alpn = "h3"; } - if (profileItem.Id.IsNullOrEmpty()) + if (profileItem.Password.IsNullOrEmpty()) { return -1; } @@ -781,17 +785,17 @@ public static class ConfigHandler profileItem.ConfigType = EConfigType.WireGuard; profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.PublicKey = profileItem.PublicKey.TrimEx(); - profileItem.Path = profileItem.Path.TrimEx(); - profileItem.RequestHost = profileItem.RequestHost.TrimEx(); - profileItem.Network = string.Empty; - if (profileItem.ShortId.IsNullOrEmpty()) + profileItem.Password = profileItem.Password.TrimEx(); + profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with { - profileItem.ShortId = Global.TunMtus.First().ToString(); - } + WgPublicKey = profileItem.GetProtocolExtra().WgPublicKey?.TrimEx(), + WgPresharedKey = profileItem.GetProtocolExtra().WgPresharedKey?.TrimEx(), + WgInterfaceAddress = profileItem.GetProtocolExtra().WgInterfaceAddress?.TrimEx(), + WgReserved = profileItem.GetProtocolExtra().WgReserved?.TrimEx(), + WgMtu = profileItem.GetProtocolExtra().WgMtu is null or <= 0 ? Global.TunMtus.First() : profileItem.GetProtocolExtra().WgMtu, + }); - if (profileItem.Id.IsNullOrEmpty()) + if (profileItem.Password.IsNullOrEmpty()) { return -1; } @@ -815,14 +819,13 @@ public static class ConfigHandler profileItem.CoreType = ECoreType.sing_box; profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Password = profileItem.Password.TrimEx(); profileItem.Network = string.Empty; if (profileItem.StreamSecurity.IsNullOrEmpty()) { profileItem.StreamSecurity = Global.StreamSecurity; } - if (profileItem.Id.IsNullOrEmpty()) + if (profileItem.Password.IsNullOrEmpty()) { return -1; } @@ -860,7 +863,7 @@ public static class ConfigHandler Remarks = t.Remarks, Address = t.Address, Port = t.Port, - Security = t.Security, + //Security = t.Security, Network = t.Network, StreamSecurity = t.StreamSecurity, Delay = t33?.Delay ?? 0, @@ -959,26 +962,25 @@ public static class ConfigHandler profileItem.ConfigType = EConfigType.VLESS; profileItem.Address = profileItem.Address.TrimEx(); - profileItem.Id = profileItem.Id.TrimEx(); - profileItem.Security = profileItem.Security.TrimEx(); + profileItem.Password = profileItem.Password.TrimEx(); profileItem.Network = profileItem.Network.TrimEx(); profileItem.HeaderType = profileItem.HeaderType.TrimEx(); profileItem.RequestHost = profileItem.RequestHost.TrimEx(); profileItem.Path = profileItem.Path.TrimEx(); profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx(); - if (!Global.Flows.Contains(profileItem.Flow)) + var vlessEncryption = profileItem.GetProtocolExtra().VlessEncryption?.TrimEx(); + var flow = profileItem.GetProtocolExtra().Flow?.TrimEx() ?? string.Empty; + profileItem.SetProtocolExtra(profileItem.GetProtocolExtra() with { - profileItem.Flow = Global.Flows.First(); - } - if (profileItem.Id.IsNullOrEmpty()) + VlessEncryption = vlessEncryption.IsNullOrEmpty() ? Global.None : vlessEncryption, + Flow = Global.Flows.Contains(flow) ? flow : Global.Flows.First(), + }); + + if (profileItem.Password.IsNullOrEmpty()) { return -1; } - if (profileItem.Security.IsNullOrEmpty()) - { - profileItem.Security = Global.None; - } await AddServerCommon(config, profileItem, toFile); @@ -1033,7 +1035,7 @@ public static class ConfigHandler /// 0 if successful public static async Task AddServerCommon(Config config, ProfileItem profileItem, bool toFile = true) { - profileItem.ConfigVersion = 2; + profileItem.ConfigVersion = 3; if (profileItem.StreamSecurity.IsNotEmpty()) { @@ -1077,42 +1079,12 @@ public static class ConfigHandler if (toFile) { + profileItem.SetProtocolExtra(); await SQLiteHelper.Instance.ReplaceAsync(profileItem); } return 0; } - public static async Task AddGroupServerCommon(Config config, ProfileItem profileItem, ProfileGroupItem profileGroupItem, bool toFile = true) - { - var maxSort = -1; - if (profileItem.IndexId.IsNullOrEmpty()) - { - profileItem.IndexId = Utils.GetGuid(false); - maxSort = ProfileExManager.Instance.GetMaxSort(); - } - var groupType = profileItem.ConfigType == EConfigType.ProxyChain ? EConfigType.ProxyChain.ToString() : profileGroupItem.MultipleLoad.ToString(); - profileItem.Address = $"{profileItem.CoreType}-{groupType}"; - if (maxSort > 0) - { - ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1); - } - if (toFile) - { - await SQLiteHelper.Instance.ReplaceAsync(profileItem); - if (profileGroupItem != null) - { - profileGroupItem.IndexId = profileItem.IndexId; - await ProfileGroupItemManager.Instance.SaveItemAsync(profileGroupItem); - } - else - { - ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(profileItem.IndexId); - await ProfileGroupItemManager.Instance.SaveTo(); - } - } - return 0; - } - /// /// Compare two profile items to determine if they represent the same server /// Used for deduplication and server matching @@ -1128,17 +1100,23 @@ public static class ConfigHandler return false; } + var oProtocolExtra = o.GetProtocolExtra(); + var nProtocolExtra = n.GetProtocolExtra(); + return o.ConfigType == n.ConfigType && AreEqual(o.Address, n.Address) && o.Port == n.Port - && AreEqual(o.Id, n.Id) - && AreEqual(o.Security, n.Security) + && AreEqual(o.Password, n.Password) + && AreEqual(oProtocolExtra.VlessEncryption, nProtocolExtra.VlessEncryption) + && AreEqual(oProtocolExtra.SsMethod, nProtocolExtra.SsMethod) + && AreEqual(oProtocolExtra.VmessSecurity, nProtocolExtra.VmessSecurity) && AreEqual(o.Network, n.Network) && AreEqual(o.HeaderType, n.HeaderType) && AreEqual(o.RequestHost, n.RequestHost) && AreEqual(o.Path, n.Path) && (o.ConfigType == EConfigType.Trojan || o.StreamSecurity == n.StreamSecurity) - && AreEqual(o.Flow, n.Flow) + && AreEqual(oProtocolExtra.Flow, nProtocolExtra.Flow) + && AreEqual(oProtocolExtra.SalamanderPass, nProtocolExtra.SalamanderPass) && AreEqual(o.Sni, n.Sni) && AreEqual(o.Alpn, n.Alpn) && AreEqual(o.Fingerprint, n.Fingerprint) @@ -1199,7 +1177,7 @@ public static class ConfigHandler var indexId = Utils.GetGuid(false); var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList()); - var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId)).Remarks} "; + var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId))?.Remarks} "; if (coreType == ECoreType.Xray) { remark += multipleLoad switch @@ -1233,13 +1211,12 @@ public static class ConfigHandler { profile.Subid = subId; } - var profileGroup = new ProfileGroupItem + var extraItem = new ProtocolExtraItem { - ChildItems = childProfileIndexId, - MultipleLoad = multipleLoad, - IndexId = indexId, + ChildItems = childProfileIndexId, MultipleLoad = multipleLoad, }; - var ret = await AddGroupServerCommon(config, profile, profileGroup, true); + profile.SetProtocolExtra(extraItem); + var ret = await AddServerCommon(config, profile, true); result.Success = ret == 0; result.Data = indexId; return result; @@ -1261,7 +1238,7 @@ public static class ConfigHandler var tun2SocksAddress = node.Address; if (node.ConfigType.IsGroupType()) { - var lstAddresses = (await ProfileGroupItemManager.GetAllChildDomainAddresses(node.IndexId)).ToList(); + var lstAddresses = (await GroupProfileManager.GetAllChildDomainAddresses(node)).ToList(); if (lstAddresses.Count > 0) { tun2SocksAddress = Utils.List2String(lstAddresses); diff --git a/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs index f098b6a4..d94cbea0 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs @@ -20,7 +20,7 @@ public class AnytlsFmt : BaseFmt Port = parsedUrl.Port, }; var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); - item.Id = rawUserInfo; + item.Password = rawUserInfo; var query = Utils.ParseQueryString(parsedUrl.Query); ResolveUriQuery(query, ref item); @@ -39,7 +39,7 @@ public class AnytlsFmt : BaseFmt { remark = "#" + Utils.UrlEncode(item.Remarks); } - var pw = item.Id; + var pw = item.Password; var dicQuery = new Dictionary(); ToUriQuery(item, Global.None, ref dicQuery); diff --git a/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs index 8bd3c3de..bfafce13 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs @@ -21,11 +21,6 @@ public class BaseFmt protected static int ToUriQuery(ProfileItem item, string? securityDef, ref Dictionary dicQuery) { - if (item.Flow.IsNotEmpty()) - { - dicQuery.Add("flow", item.Flow); - } - if (item.StreamSecurity.IsNotEmpty()) { dicQuery.Add("security", item.StreamSecurity); @@ -208,7 +203,6 @@ public class BaseFmt protected static int ResolveUriQuery(NameValueCollection query, ref ProfileItem item) { - item.Flow = GetQueryValue(query, "flow"); item.StreamSecurity = GetQueryValue(query, "security"); item.Sni = GetQueryValue(query, "sni"); item.Alpn = GetQueryDecoded(query, "alpn"); diff --git a/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs b/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs index d92bd0c8..401beda7 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs @@ -19,16 +19,19 @@ public class Hysteria2Fmt : BaseFmt item.Address = url.IdnHost; item.Port = url.Port; item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); - item.Id = Utils.UrlDecode(url.UserInfo); + item.Password = Utils.UrlDecode(url.UserInfo); var query = Utils.ParseQueryString(url.Query); ResolveUriQuery(query, ref item); - item.Path = GetQueryDecoded(query, "obfs-password"); - item.Ports = GetQueryDecoded(query, "mport"); if (item.CertSha.IsNullOrEmpty()) { item.CertSha = GetQueryDecoded(query, "pinSHA256"); } + item.SetProtocolExtra(item.GetProtocolExtra() with + { + Ports = GetQueryDecoded(query, "mport"), + SalamanderPass = GetQueryDecoded(query, "obfs-password"), + }); return item; } @@ -49,15 +52,16 @@ public class Hysteria2Fmt : BaseFmt } var dicQuery = new Dictionary(); ToUriQueryLite(item, ref dicQuery); + var protocolExtraItem = item.GetProtocolExtra(); - if (item.Path.IsNotEmpty()) + if (!protocolExtraItem.SalamanderPass.IsNullOrEmpty()) { dicQuery.Add("obfs", "salamander"); - dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path)); + dicQuery.Add("obfs-password", Utils.UrlEncode(protocolExtraItem.SalamanderPass)); } - if (item.Ports.IsNotEmpty()) + if (!protocolExtraItem.Ports.IsNullOrEmpty()) { - dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-'))); + dicQuery.Add("mport", Utils.UrlEncode(protocolExtraItem.Ports.Replace(':', '-'))); } if (!item.CertSha.IsNullOrEmpty()) { @@ -70,7 +74,7 @@ public class Hysteria2Fmt : BaseFmt dicQuery.Add("pinSHA256", Utils.UrlEncode(sha)); } - return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Id, dicQuery, remark); + return ToUri(EConfigType.Hysteria2, item.Address, item.Port, item.Password, dicQuery, remark); } public static ProfileItem? ResolveFull2(string strData, string? subRemarks) diff --git a/v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs index 62f9e120..5b30fa46 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs @@ -12,7 +12,8 @@ public class ShadowsocksFmt : BaseFmt { return null; } - if (item.Address.Length == 0 || item.Port == 0 || item.Security.Length == 0 || item.Id.Length == 0) + + if (item.Address.Length == 0 || item.Port == 0 || item.GetProtocolExtra().SsMethod.IsNullOrEmpty() || item.Password.Length == 0) { return null; } @@ -40,7 +41,7 @@ public class ShadowsocksFmt : BaseFmt // item.port); //url = Utile.Base64Encode(url); //new Sip002 - var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true); + var pw = Utils.Base64Encode($"{item.GetProtocolExtra().SsMethod}:{item.Password}", true); // plugin var plugin = string.Empty; @@ -136,8 +137,8 @@ public class ShadowsocksFmt : BaseFmt { return null; } - item.Security = details.Groups["method"].Value; - item.Id = details.Groups["password"].Value; + item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = details.Groups["method"].Value }); + item.Password = details.Groups["password"].Value; item.Address = details.Groups["hostname"].Value; item.Port = details.Groups["port"].Value.ToInt(); return item; @@ -166,8 +167,8 @@ public class ShadowsocksFmt : BaseFmt { return null; } - item.Security = userInfoParts.First(); - item.Id = Utils.UrlDecode(userInfoParts.Last()); + item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() }); + item.Password = Utils.UrlDecode(userInfoParts.Last()); } else { @@ -178,8 +179,8 @@ public class ShadowsocksFmt : BaseFmt { return null; } - item.Security = userInfoParts.First(); - item.Id = userInfoParts.Last(); + item.SetProtocolExtra(item.GetProtocolExtra() with { SsMethod = userInfoParts.First() }); + item.Password = userInfoParts.Last(); } var queryParameters = Utils.ParseQueryString(parsedUrl.Query); @@ -275,7 +276,6 @@ public class ShadowsocksFmt : BaseFmt } } } - return item; } @@ -300,11 +300,11 @@ public class ShadowsocksFmt : BaseFmt var ssItem = new ProfileItem() { Remarks = it.remarks, - Security = it.method, - Id = it.password, + Password = it.password, Address = it.server, Port = it.server_port.ToInt() }; + ssItem.SetProtocolExtra(new ProtocolExtraItem() { SsMethod = it.method }); lst.Add(ssItem); } return lst; diff --git a/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs index ae837793..b3a54301 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/SocksFmt.cs @@ -33,7 +33,7 @@ public class SocksFmt : BaseFmt remark = "#" + Utils.UrlEncode(item.Remarks); } //new - var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true); + var pw = Utils.Base64Encode($"{item.Username}:{item.Password}", true); return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark); } @@ -78,9 +78,8 @@ public class SocksFmt : BaseFmt } item.Address = arr1[1][..indexPort]; item.Port = arr1[1][(indexPort + 1)..].ToInt(); - item.Security = arr21.First(); - item.Id = arr21[1]; - + item.Username = arr21.First(); + item.Password = arr21[1]; return item; } @@ -98,15 +97,14 @@ public class SocksFmt : BaseFmt Address = parsedUrl.IdnHost, Port = parsedUrl.Port, }; - // parse base64 UserInfo var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo); var userInfo = Utils.Base64Decode(rawUserInfo); var userInfoParts = userInfo.Split([':'], 2); if (userInfoParts.Length == 2) { - item.Security = userInfoParts.First(); - item.Id = userInfoParts[1]; + item.Username = userInfoParts.First(); + item.Password = userInfoParts[1]; } return item; diff --git a/v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs index dc4794d8..630dead1 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/TrojanFmt.cs @@ -20,9 +20,10 @@ public class TrojanFmt : BaseFmt item.Address = url.IdnHost; item.Port = url.Port; item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); - item.Id = Utils.UrlDecode(url.UserInfo); + item.Password = Utils.UrlDecode(url.UserInfo); var query = Utils.ParseQueryString(url.Query); + item.SetProtocolExtra(item.GetProtocolExtra() with { Flow = GetQueryValue(query, "flow") }); ResolveUriQuery(query, ref item); return item; @@ -40,8 +41,12 @@ public class TrojanFmt : BaseFmt remark = "#" + Utils.UrlEncode(item.Remarks); } var dicQuery = new Dictionary(); + if (!item.GetProtocolExtra().Flow.IsNullOrEmpty()) + { + dicQuery.Add("flow", item.GetProtocolExtra().Flow); + } ToUriQuery(item, null, ref dicQuery); - return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Id, dicQuery, remark); + return ToUri(EConfigType.Trojan, item.Address, item.Port, item.Password, dicQuery, remark); } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs index 1c5aded6..56f78ea9 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs @@ -24,8 +24,8 @@ public class TuicFmt : BaseFmt var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2); if (userInfoParts.Length == 2) { - item.Id = userInfoParts.First(); - item.Security = userInfoParts.Last(); + item.Username = userInfoParts.First(); + item.Password = userInfoParts.Last(); } var query = Utils.ParseQueryString(url.Query); @@ -53,6 +53,6 @@ public class TuicFmt : BaseFmt dicQuery.Add("congestion_control", item.HeaderType); - return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark); + return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Username ?? ""}:{item.Password}", dicQuery, remark); } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs index 3048b51c..a3463bcd 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/VLESSFmt.cs @@ -9,7 +9,6 @@ public class VLESSFmt : BaseFmt ProfileItem item = new() { ConfigType = EConfigType.VLESS, - Security = Global.None }; var url = Utils.TryUri(str); @@ -21,10 +20,14 @@ public class VLESSFmt : BaseFmt item.Address = url.IdnHost; item.Port = url.Port; item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); - item.Id = Utils.UrlDecode(url.UserInfo); + item.Password = Utils.UrlDecode(url.UserInfo); var query = Utils.ParseQueryString(url.Query); - item.Security = GetQueryValue(query, "encryption", Global.None); + item.SetProtocolExtra(item.GetProtocolExtra() with + { + VlessEncryption = GetQueryValue(query, "encryption", Global.None), + Flow = GetQueryValue(query, "flow") + }); item.StreamSecurity = GetQueryValue(query, "security"); ResolveUriQuery(query, ref item); @@ -44,16 +47,14 @@ public class VLESSFmt : BaseFmt remark = "#" + Utils.UrlEncode(item.Remarks); } var dicQuery = new Dictionary(); - if (item.Security.IsNotEmpty()) + dicQuery.Add("encryption", + !item.GetProtocolExtra().VlessEncryption.IsNullOrEmpty() ? item.GetProtocolExtra().VlessEncryption : Global.None); + if (!item.GetProtocolExtra().Flow.IsNullOrEmpty()) { - dicQuery.Add("encryption", item.Security); - } - else - { - dicQuery.Add("encryption", Global.None); + dicQuery.Add("flow", item.GetProtocolExtra().Flow); } ToUriQuery(item, Global.None, ref dicQuery); - return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Id, dicQuery, remark); + return ToUri(EConfigType.VLESS, item.Address, item.Port, item.Password, dicQuery, remark); } } diff --git a/v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs index e6535d6e..b8760a40 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/VmessFmt.cs @@ -23,15 +23,16 @@ public class VmessFmt : BaseFmt { return null; } + var vmessQRCode = new VmessQRCode { - v = item.ConfigVersion, + v = 2, ps = item.Remarks.TrimEx(), add = item.Address, port = item.Port, - id = item.Id, - aid = item.AlterId, - scy = item.Security, + id = item.Password, + aid = int.TryParse(item.GetProtocolExtra()?.AlterId, out var result) ? result : 0, + scy = item.GetProtocolExtra().VmessSecurity ?? "", net = item.Network, type = item.HeaderType, host = item.RequestHost, @@ -71,15 +72,16 @@ public class VmessFmt : BaseFmt item.Network = Global.DefaultNetwork; item.HeaderType = Global.None; - item.ConfigVersion = vmessQRCode.v; + //item.ConfigVersion = vmessQRCode.v; item.Remarks = Utils.ToString(vmessQRCode.ps); item.Address = Utils.ToString(vmessQRCode.add); item.Port = vmessQRCode.port; - item.Id = Utils.ToString(vmessQRCode.id); - item.AlterId = vmessQRCode.aid; - item.Security = Utils.ToString(vmessQRCode.scy); - - item.Security = vmessQRCode.scy.IsNotEmpty() ? vmessQRCode.scy : Global.DefaultSecurity; + item.Password = Utils.ToString(vmessQRCode.id); + item.SetProtocolExtra(new ProtocolExtraItem + { + AlterId = vmessQRCode.aid.ToString(), + VmessSecurity = vmessQRCode.scy.IsNullOrEmpty() ? Global.DefaultSecurity : vmessQRCode.scy, + }); if (vmessQRCode.net.IsNotEmpty()) { item.Network = vmessQRCode.net; @@ -105,7 +107,6 @@ public class VmessFmt : BaseFmt var item = new ProfileItem { ConfigType = EConfigType.VMess, - Security = "auto" }; var url = Utils.TryUri(str); @@ -117,7 +118,12 @@ public class VmessFmt : BaseFmt item.Address = url.IdnHost; item.Port = url.Port; item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); - item.Id = Utils.UrlDecode(url.UserInfo); + item.Password = Utils.UrlDecode(url.UserInfo); + + item.SetProtocolExtra(new ProtocolExtraItem + { + VmessSecurity = "auto", + }); var query = Utils.ParseQueryString(url.Query); ResolveUriQuery(query, ref item); diff --git a/v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs index 6ceb945d..73cb6a85 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/WireguardFmt.cs @@ -20,14 +20,17 @@ public class WireguardFmt : BaseFmt item.Address = url.IdnHost; item.Port = url.Port; item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped); - item.Id = Utils.UrlDecode(url.UserInfo); + item.Password = Utils.UrlDecode(url.UserInfo); var query = Utils.ParseQueryString(url.Query); - item.PublicKey = GetQueryDecoded(query, "publickey"); - item.Path = GetQueryDecoded(query, "reserved"); - item.RequestHost = GetQueryDecoded(query, "address"); - item.ShortId = GetQueryDecoded(query, "mtu"); + item.SetProtocolExtra(item.GetProtocolExtra() with + { + WgPublicKey = GetQueryDecoded(query, "publickey"), + WgReserved = GetQueryDecoded(query, "reserved"), + WgInterfaceAddress = GetQueryDecoded(query, "address"), + WgMtu = int.TryParse(GetQueryDecoded(query, "mtu"), out var mtuVal) ? mtuVal : 1280, + }); return item; } @@ -46,22 +49,19 @@ public class WireguardFmt : BaseFmt } var dicQuery = new Dictionary(); - if (item.PublicKey.IsNotEmpty()) + if (!item.GetProtocolExtra().WgPublicKey.IsNullOrEmpty()) { - dicQuery.Add("publickey", Utils.UrlEncode(item.PublicKey)); + dicQuery.Add("publickey", Utils.UrlEncode(item.GetProtocolExtra().WgPublicKey)); } - if (item.Path.IsNotEmpty()) + if (!item.GetProtocolExtra().WgReserved.IsNullOrEmpty()) { - dicQuery.Add("reserved", Utils.UrlEncode(item.Path)); + dicQuery.Add("reserved", Utils.UrlEncode(item.GetProtocolExtra().WgReserved)); } - if (item.RequestHost.IsNotEmpty()) + if (!item.GetProtocolExtra().WgInterfaceAddress.IsNullOrEmpty()) { - dicQuery.Add("address", Utils.UrlEncode(item.RequestHost)); + dicQuery.Add("address", Utils.UrlEncode(item.GetProtocolExtra().WgInterfaceAddress)); } - if (item.ShortId.IsNotEmpty()) - { - dicQuery.Add("mtu", Utils.UrlEncode(item.ShortId)); - } - return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Id, dicQuery, remark); + dicQuery.Add("mtu", Utils.UrlEncode(item.GetProtocolExtra().WgMtu > 0 ? item.GetProtocolExtra().WgMtu.ToString() : "1280")); + return ToUri(EConfigType.WireGuard, item.Address, item.Port, item.Password, dicQuery, remark); } } diff --git a/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs b/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs index 965335cc..ee3cd5a0 100644 --- a/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs +++ b/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs @@ -128,23 +128,25 @@ public class ActionPrecheckManager } } + var protocolExtra = item.GetProtocolExtra(); + switch (item.ConfigType) { case EConfigType.VMess: - if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id)) + if (item.Password.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Password)) { - errors.Add(string.Format(ResUI.InvalidProperty, "Id")); + errors.Add(string.Format(ResUI.InvalidProperty, "Password")); } break; case EConfigType.VLESS: - if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30)) + if (item.Password.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Password) && item.Password.Length > 30)) { - errors.Add(string.Format(ResUI.InvalidProperty, "Id")); + errors.Add(string.Format(ResUI.InvalidProperty, "Password")); } - if (!Global.Flows.Contains(item.Flow)) + if (!Global.Flows.Contains(protocolExtra.Flow ?? string.Empty)) { errors.Add(string.Format(ResUI.InvalidProperty, "Flow")); } @@ -152,14 +154,14 @@ public class ActionPrecheckManager break; case EConfigType.Shadowsocks: - if (item.Id.IsNullOrEmpty()) + if (item.Password.IsNullOrEmpty()) { - errors.Add(string.Format(ResUI.InvalidProperty, "Id")); + errors.Add(string.Format(ResUI.InvalidProperty, "Password")); } - if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security)) + if (string.IsNullOrEmpty(protocolExtra.SsMethod) || !Global.SsSecuritiesInSingbox.Contains(protocolExtra.SsMethod)) { - errors.Add(string.Format(ResUI.InvalidProperty, "Security")); + errors.Add(string.Format(ResUI.InvalidProperty, "SsMethod")); } break; @@ -202,37 +204,22 @@ public class ActionPrecheckManager { var errors = new List(); - ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group); - if (group is null || group.NotHasChild()) - { - errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks)); - return errors; - } - - var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId); + var hasCycle = await GroupProfileManager.HasCycle(item.IndexId, item.GetProtocolExtra()); if (hasCycle) { errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks)); return errors; } - var childIds = new List(); - var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group); - childIds.AddRangeSafe(subItems.Select(p => p.IndexId)); - childIds.AddRangeSafe(Utils.String2List(group.ChildItems)); + var (childItems, _) = await GroupProfileManager.GetChildProfileItems(item); - foreach (var child in childIds) + foreach (var childItem in childItems) { var childErrors = new List(); - if (child.IsNullOrEmpty()) - { - continue; - } - var childItem = await AppManager.Instance.GetProfileItem(child); if (childItem is null) { - childErrors.Add(string.Format(ResUI.NodeTagNotExist, child)); + childErrors.Add(string.Format(ResUI.NodeTagNotExist, "")); continue; } diff --git a/v2rayN/ServiceLib/Manager/AppManager.cs b/v2rayN/ServiceLib/Manager/AppManager.cs index 6c20022a..576af0d0 100644 --- a/v2rayN/ServiceLib/Manager/AppManager.cs +++ b/v2rayN/ServiceLib/Manager/AppManager.cs @@ -81,7 +81,9 @@ public sealed class AppManager SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); +#pragma warning disable CS0618 SQLiteHelper.Instance.CreateTable(); +#pragma warning restore CS0618 return true; } @@ -94,6 +96,11 @@ public sealed class AppManager _ = StatePort; _ = StatePort2; + Task.Run(async () => + { + await MigrateProfileExtra(); + }).Wait(); + return true; } @@ -225,15 +232,6 @@ public sealed class AppManager return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.Remarks == remarks); } - public async Task GetProfileGroupItem(string indexId) - { - if (indexId.IsNullOrEmpty()) - { - return null; - } - return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.IndexId == indexId); - } - public async Task?> RoutingItems() { return await SQLiteHelper.Instance.TableAsync().OrderBy(t => t.Sort).ToListAsync(); @@ -264,6 +262,119 @@ public sealed class AppManager return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.CoreType == eCoreType); } + public async Task MigrateProfileExtra() + { +#pragma warning disable CS0618 + var list = await SQLiteHelper.Instance.TableAsync().ToListAsync(); + var groupItems = new ConcurrentDictionary(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!)); + + const int pageSize = 500; + var offset = 0; + + while (true) + { + var sql = $"SELECT * FROM ProfileItem WHERE ConfigVersion < 3 LIMIT {pageSize} OFFSET {offset}"; + var batch = await SQLiteHelper.Instance.QueryAsync(sql); + if (batch is null || batch.Count == 0) + { + break; + } + + var batchSuccessCount = 0; + foreach (var item in batch) + { + try + { + var extra = item.GetProtocolExtra(); + + if (item.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain) + { + extra = extra with { GroupType = nameof(item.ConfigType) }; + groupItems.TryGetValue(item.IndexId, out var groupItem); + if (groupItem != null && !groupItem.NotHasChild()) + { + extra = extra with + { + ChildItems = groupItem.ChildItems, + SubChildItems = groupItem.SubChildItems, + Filter = groupItem.Filter, + MultipleLoad = groupItem.MultipleLoad, + }; + } + } + + switch (item.ConfigType) + { + case EConfigType.Shadowsocks: + extra = extra with { SsMethod = item.Security.NullIfEmpty() }; + break; + case EConfigType.VMess: + extra = extra with + { + AlterId = item.AlterId.ToString(), + VmessSecurity = item.Security.NullIfEmpty(), + }; + break; + case EConfigType.VLESS: + extra = extra with + { + Flow = item.Flow.NullIfEmpty(), + VlessEncryption = item.Security, + }; + break; + case EConfigType.Hysteria2: + extra = extra with + { + SalamanderPass = item.Path.NullIfEmpty(), + Ports = item.Ports.NullIfEmpty(), + UpMbps = _config.HysteriaItem.UpMbps, + DownMbps = _config.HysteriaItem.DownMbps, + HopInterval = _config.HysteriaItem.HopInterval.ToString(), + }; + break; + case EConfigType.TUIC: + item.Username = item.Id; + item.Id = item.Security; + item.Password = item.Security; + break; + case EConfigType.HTTP: + case EConfigType.SOCKS: + item.Username = item.Security; + break; + case EConfigType.WireGuard: + extra = extra with + { + WgPublicKey = item.PublicKey.NullIfEmpty(), + WgInterfaceAddress = item.RequestHost.NullIfEmpty(), + WgReserved = item.Path.NullIfEmpty(), + WgMtu = int.TryParse(item.ShortId, out var mtu) ? mtu : 1280 + }; + break; + } + + item.SetProtocolExtra(extra); + + item.Password = item.Id; + + item.ConfigVersion = 3; + await SQLiteHelper.Instance.UpdateAsync(item); + batchSuccessCount++; + } + catch (Exception ex) + { + Logging.SaveLog($"MigrateProfileExtra Error: {ex}"); + } + } + + // Only increment offset by the number of failed items that remain in the result set + // Successfully updated items are automatically excluded from future queries due to ConfigVersion = 3 + offset += batch.Count - batchSuccessCount; + } + + //await ProfileGroupItemManager.Instance.ClearAll(); +#pragma warning restore CS0618 + } + #endregion SqliteHelper #region Core Type diff --git a/v2rayN/ServiceLib/Manager/GroupProfileManager.cs b/v2rayN/ServiceLib/Manager/GroupProfileManager.cs new file mode 100644 index 00000000..3c9d766e --- /dev/null +++ b/v2rayN/ServiceLib/Manager/GroupProfileManager.cs @@ -0,0 +1,180 @@ +namespace ServiceLib.Manager; + +public class GroupProfileManager +{ + public static async Task HasCycle(ProfileItem item) + { + return await HasCycle(item.IndexId, item.GetProtocolExtra()); + } + + public static async Task HasCycle(string? indexId, ProtocolExtraItem? extraInfo) + { + return await HasCycle(indexId, extraInfo, new HashSet(), new HashSet()); + } + + public static async Task HasCycle(string? indexId, ProtocolExtraItem? extraInfo, HashSet visited, HashSet stack) + { + if (indexId.IsNullOrEmpty() || extraInfo == null) + { + return false; + } + + if (stack.Contains(indexId)) + { + return true; + } + + if (visited.Contains(indexId)) + { + return false; + } + + visited.Add(indexId); + stack.Add(indexId); + + try + { + if (extraInfo.GroupType.IsNullOrEmpty()) + { + return false; + } + + if (extraInfo.ChildItems.IsNullOrEmpty()) + { + return false; + } + + var childIds = Utils.String2List(extraInfo.ChildItems) + ?.Where(p => !string.IsNullOrEmpty(p)) + .ToList(); + if (childIds == null) + { + return false; + } + + foreach (var child in childIds) + { + var childItem = await AppManager.Instance.GetProfileItem(child); + if (await HasCycle(child, childItem?.GetProtocolExtra(), visited, stack)) + { + return true; + } + } + + return false; + } + finally + { + stack.Remove(indexId); + } + } + + public static async Task<(List Items, ProtocolExtraItem? Extra)> GetChildProfileItems(ProfileItem profileItem) + { + var protocolExtra = profileItem?.GetProtocolExtra(); + return (await GetChildProfileItemsByProtocolExtra(protocolExtra), protocolExtra); + } + + public static async Task> GetChildProfileItemsByProtocolExtra(ProtocolExtraItem? protocolExtra) + { + if (protocolExtra == null) + { + return new(); + } + var items = await GetSelectedChildProfileItems(protocolExtra); + var subItems = await GetSubChildProfileItems(protocolExtra); + items.AddRange(subItems); + + return items; + } + + public static async Task> GetSelectedChildProfileItems(ProtocolExtraItem? extra) + { + if (extra == null || extra.ChildItems.IsNullOrEmpty()) + { + return new(); + } + var childProfiles = (await Task.WhenAll( + (Utils.String2List(extra.ChildItems) ?? new()) + .Where(p => !p.IsNullOrEmpty()) + .Select(AppManager.Instance.GetProfileItem) + )) + .Where(p => + p != null && + p.IsValid() && + p.ConfigType != EConfigType.Custom + ) + .ToList(); + return childProfiles; + } + + public static async Task> GetSubChildProfileItems(ProtocolExtraItem? extra) + { + if (extra == null || extra.SubChildItems.IsNullOrEmpty()) + { + return new(); + } + var childProfiles = await AppManager.Instance.ProfileItems(extra.SubChildItems ?? string.Empty); + + return childProfiles?.Where(p => + p != null && + p.IsValid() && + !p.ConfigType.IsComplexType() && + (extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter)) + ) + .ToList() ?? new(); + } + + public static async Task> GetAllChildDomainAddresses(ProfileItem profileItem) + { + var childAddresses = new HashSet(); + var (childItems, _) = await GetChildProfileItems(profileItem); + foreach (var child in childItems) + { + if (!child.IsComplex()) + { + childAddresses.Add(child.Address); + } + else if (child.ConfigType.IsGroupType()) + { + var subAddresses = await GetAllChildDomainAddresses(child); + foreach (var addr in subAddresses) + { + childAddresses.Add(addr); + } + } + } + return childAddresses; + } + + public static async Task> GetAllChildEchQuerySni(ProfileItem profileItem) + { + var childAddresses = new HashSet(); + var (childItems, _) = await GetChildProfileItems(profileItem); + foreach (var childNode in childItems) + { + if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty()) + { + if (childNode.StreamSecurity == Global.StreamSecurity + && childNode.EchConfigList?.Contains("://") == true) + { + var idx = childNode.EchConfigList.IndexOf('+'); + childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni); + } + else + { + childAddresses.Add(childNode.Sni); + } + } + else if (childNode.ConfigType.IsGroupType()) + { + var subAddresses = await GetAllChildDomainAddresses(childNode); + foreach (var addr in subAddresses) + { + childAddresses.Add(addr); + } + } + } + return childAddresses; + } +} diff --git a/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs b/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs deleted file mode 100644 index 041b1c78..00000000 --- a/v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs +++ /dev/null @@ -1,388 +0,0 @@ -namespace ServiceLib.Manager; - -public class ProfileGroupItemManager -{ - private static readonly Lazy _instance = new(() => new()); - private ConcurrentDictionary _items = new(); - - public static ProfileGroupItemManager Instance => _instance.Value; - private static readonly string _tag = "ProfileGroupItemManager"; - - private ProfileGroupItemManager() - { - } - - public async Task Init() - { - await InitData(); - } - - // Read-only getters: do not create or mark dirty - public bool TryGet(string indexId, out ProfileGroupItem? item) - { - item = null; - if (string.IsNullOrWhiteSpace(indexId)) - { - return false; - } - - return _items.TryGetValue(indexId, out item); - } - - public ProfileGroupItem? GetOrDefault(string indexId) - { - return string.IsNullOrWhiteSpace(indexId) ? null : (_items.TryGetValue(indexId, out var v) ? v : null); - } - - private async Task InitData() - { - await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem where IndexId not in ( select indexId from ProfileItem )"); - - var list = await SQLiteHelper.Instance.TableAsync().ToListAsync(); - _items = new ConcurrentDictionary(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!)); - } - - private ProfileGroupItem AddProfileGroupItem(string indexId) - { - var profileGroupItem = new ProfileGroupItem() - { - IndexId = indexId, - ChildItems = string.Empty, - MultipleLoad = EMultipleLoad.LeastPing - }; - - _items[indexId] = profileGroupItem; - return profileGroupItem; - } - - private ProfileGroupItem GetProfileGroupItem(string indexId) - { - if (string.IsNullOrEmpty(indexId)) - { - indexId = Utils.GetGuid(false); - } - - return _items.GetOrAdd(indexId, AddProfileGroupItem); - } - - public async Task ClearAll() - { - await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem "); - _items.Clear(); - } - - public async Task SaveTo() - { - try - { - var lstExists = await SQLiteHelper.Instance.TableAsync().ToListAsync(); - var existsMap = lstExists.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!); - - var lstInserts = new List(); - var lstUpdates = new List(); - - foreach (var item in _items.Values) - { - if (string.IsNullOrEmpty(item.IndexId)) - { - continue; - } - - if (existsMap.ContainsKey(item.IndexId)) - { - lstUpdates.Add(item); - } - else - { - lstInserts.Add(item); - } - } - - try - { - if (lstInserts.Count > 0) - { - await SQLiteHelper.Instance.InsertAllAsync(lstInserts); - } - - if (lstUpdates.Count > 0) - { - await SQLiteHelper.Instance.UpdateAllAsync(lstUpdates); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - } - - public ProfileGroupItem GetOrCreateAndMarkDirty(string indexId) - { - return GetProfileGroupItem(indexId); - } - - public async ValueTask DisposeAsync() - { - await SaveTo(); - } - - public async Task SaveItemAsync(ProfileGroupItem item) - { - if (item is null) - { - throw new ArgumentNullException(nameof(item)); - } - - if (string.IsNullOrWhiteSpace(item.IndexId)) - { - throw new ArgumentException("IndexId required", nameof(item)); - } - - _items[item.IndexId] = item; - - try - { - var lst = await SQLiteHelper.Instance.TableAsync().Where(t => t.IndexId == item.IndexId).ToListAsync(); - if (lst != null && lst.Count > 0) - { - await SQLiteHelper.Instance.UpdateAllAsync(new List { item }); - } - else - { - await SQLiteHelper.Instance.InsertAllAsync(new List { item }); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - } - - #region Helper - - public static bool HasCycle(string? indexId) - { - return HasCycle(indexId, new HashSet(), new HashSet()); - } - - public static bool HasCycle(string? indexId, HashSet visited, HashSet stack) - { - if (indexId.IsNullOrEmpty()) - { - return false; - } - - if (stack.Contains(indexId)) - { - return true; - } - - if (visited.Contains(indexId)) - { - return false; - } - - visited.Add(indexId); - stack.Add(indexId); - - try - { - Instance.TryGet(indexId, out var groupItem); - - if (groupItem == null || groupItem.ChildItems.IsNullOrEmpty()) - { - return false; - } - - var childIds = Utils.String2List(groupItem.ChildItems) - .Where(p => !string.IsNullOrEmpty(p)) - .ToList(); - if (childIds == null) - { - return false; - } - - foreach (var child in childIds) - { - if (HasCycle(child, visited, stack)) - { - return true; - } - } - - return false; - } - finally - { - stack.Remove(indexId); - } - } - - public static async Task<(List Items, ProfileGroupItem? Group)> GetChildProfileItems(string? indexId) - { - Instance.TryGet(indexId, out var profileGroupItem); - if (profileGroupItem == null || profileGroupItem.NotHasChild()) - { - return (new List(), profileGroupItem); - } - - var items = new List(); - items.AddRange(await GetSubChildProfileItems(profileGroupItem)); - items.AddRange(await GetChildProfileItems(profileGroupItem)); - - return (items, profileGroupItem); - } - - public static async Task> GetChildProfileItems(ProfileGroupItem? group) - { - if (group == null || group.ChildItems.IsNullOrEmpty()) - { - return new(); - } - var childProfiles = (await Task.WhenAll( - Utils.String2List(group.ChildItems) - .Where(p => !p.IsNullOrEmpty()) - .Select(AppManager.Instance.GetProfileItem) - )) - .Where(p => - p != null && - p.IsValid() && - p.ConfigType != EConfigType.Custom - ) - .ToList(); - return childProfiles; - } - - public static async Task> GetSubChildProfileItems(ProfileGroupItem? group) - { - if (group == null || group.SubChildItems.IsNullOrEmpty()) - { - return new(); - } - var childProfiles = await AppManager.Instance.ProfileItems(group.SubChildItems); - - return childProfiles.Where(p => - p != null && - p.IsValid() && - !p.ConfigType.IsComplexType() && - (group.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, group.Filter)) - ) - .ToList(); - } - - public static async Task> GetAllChildDomainAddresses(string indexId) - { - // include grand children - var childAddresses = new HashSet(); - if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null) - { - return childAddresses; - } - - if (groupItem.SubChildItems.IsNotEmpty()) - { - var subItems = await GetSubChildProfileItems(groupItem); - subItems.ForEach(p => childAddresses.Add(p.Address)); - } - - var childIds = Utils.String2List(groupItem.ChildItems) ?? []; - - foreach (var childId in childIds) - { - var childNode = await AppManager.Instance.GetProfileItem(childId); - if (childNode == null) - { - continue; - } - - if (!childNode.IsComplex()) - { - childAddresses.Add(childNode.Address); - } - else if (childNode.ConfigType.IsGroupType()) - { - var subAddresses = await GetAllChildDomainAddresses(childNode.IndexId); - foreach (var addr in subAddresses) - { - childAddresses.Add(addr); - } - } - } - - return childAddresses; - } - - public static async Task> GetAllChildEchQuerySni(string indexId) - { - // include grand children - var childAddresses = new HashSet(); - if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null) - { - return childAddresses; - } - - if (groupItem.SubChildItems.IsNotEmpty()) - { - var subItems = await GetSubChildProfileItems(groupItem); - foreach (var childNode in subItems) - { - if (childNode.EchConfigList.IsNullOrEmpty()) - { - continue; - } - if (childNode.StreamSecurity == Global.StreamSecurity - && childNode.EchConfigList?.Contains("://") == true) - { - var idx = childNode.EchConfigList.IndexOf('+'); - childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni); - } - else - { - childAddresses.Add(childNode.Sni); - } - } - } - - var childIds = Utils.String2List(groupItem.ChildItems) ?? []; - - foreach (var childId in childIds) - { - var childNode = await AppManager.Instance.GetProfileItem(childId); - if (childNode == null) - { - continue; - } - - if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty()) - { - if (childNode.StreamSecurity == Global.StreamSecurity - && childNode.EchConfigList?.Contains("://") == true) - { - var idx = childNode.EchConfigList.IndexOf('+'); - childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni); - } - else - { - childAddresses.Add(childNode.Sni); - } - } - else if (childNode.ConfigType.IsGroupType()) - { - var subAddresses = await GetAllChildDomainAddresses(childNode.IndexId); - foreach (var addr in subAddresses) - { - childAddresses.Add(addr); - } - } - } - - return childAddresses; - } - - #endregion Helper -} diff --git a/v2rayN/ServiceLib/Models/ProfileGroupItem.cs b/v2rayN/ServiceLib/Models/ProfileGroupItem.cs index 12c0f899..94a9aad2 100644 --- a/v2rayN/ServiceLib/Models/ProfileGroupItem.cs +++ b/v2rayN/ServiceLib/Models/ProfileGroupItem.cs @@ -1,5 +1,6 @@ namespace ServiceLib.Models; +[Obsolete("Use ProtocolExtraItem instead.")] [Serializable] public class ProfileGroupItem { diff --git a/v2rayN/ServiceLib/Models/ProfileItem.cs b/v2rayN/ServiceLib/Models/ProfileItem.cs index b5424265..a43b6d18 100644 --- a/v2rayN/ServiceLib/Models/ProfileItem.cs +++ b/v2rayN/ServiceLib/Models/ProfileItem.cs @@ -3,16 +3,17 @@ namespace ServiceLib.Models; [Serializable] public class ProfileItem : ReactiveObject { + private ProtocolExtraItem? _protocolExtraCache; + public ProfileItem() { IndexId = string.Empty; ConfigType = EConfigType.VMess; - ConfigVersion = 2; + ConfigVersion = 3; Address = string.Empty; Port = 0; - Id = string.Empty; - AlterId = 0; - Security = string.Empty; + Password = string.Empty; + Username = string.Empty; Network = string.Empty; Remarks = string.Empty; HeaderType = string.Empty; @@ -21,7 +22,6 @@ public class ProfileItem : ReactiveObject StreamSecurity = string.Empty; AllowInsecure = string.Empty; Subid = string.Empty; - Flow = string.Empty; } #region function @@ -81,7 +81,7 @@ public class ProfileItem : ReactiveObject switch (ConfigType) { case EConfigType.VMess: - if (Id.IsNullOrEmpty() || !Utils.IsGuidByParse(Id)) + if (Password.IsNullOrEmpty() || !Utils.IsGuidByParse(Password)) { return false; } @@ -89,12 +89,12 @@ public class ProfileItem : ReactiveObject break; case EConfigType.VLESS: - if (Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(Id) && Id.Length > 30)) + if (Password.IsNullOrEmpty() || (!Utils.IsGuidByParse(Password) && Password.Length > 30)) { return false; } - if (!Global.Flows.Contains(Flow)) + if (!Global.Flows.Contains(GetProtocolExtra().Flow ?? string.Empty)) { return false; } @@ -102,12 +102,13 @@ public class ProfileItem : ReactiveObject break; case EConfigType.Shadowsocks: - if (Id.IsNullOrEmpty()) + if (Password.IsNullOrEmpty()) { return false; } - if (string.IsNullOrEmpty(Security) || !Global.SsSecuritiesInSingbox.Contains(Security)) + if (string.IsNullOrEmpty(GetProtocolExtra().SsMethod) + || !Global.SsSecuritiesInSingbox.Contains(GetProtocolExtra().SsMethod)) { return false; } @@ -125,6 +126,22 @@ public class ProfileItem : ReactiveObject return true; } + public void SetProtocolExtra(ProtocolExtraItem extraItem) + { + _protocolExtraCache = extraItem; + ProtoExtra = JsonUtils.Serialize(extraItem, false); + } + + public void SetProtocolExtra() + { + ProtoExtra = JsonUtils.Serialize(_protocolExtraCache, false); + } + + public ProtocolExtraItem GetProtocolExtra() + { + return _protocolExtraCache ??= JsonUtils.Deserialize(ProtoExtra) ?? new ProtocolExtraItem(); + } + #endregion function [PrimaryKey] @@ -134,10 +151,8 @@ public class ProfileItem : ReactiveObject public int ConfigVersion { get; set; } public string Address { get; set; } public int Port { get; set; } - public string Ports { get; set; } - public string Id { get; set; } - public int AlterId { get; set; } - public string Security { get; set; } + public string Password { get; set; } + public string Username { get; set; } public string Network { get; set; } public string Remarks { get; set; } public string HeaderType { get; set; } @@ -147,7 +162,6 @@ public class ProfileItem : ReactiveObject public string AllowInsecure { get; set; } public string Subid { get; set; } public bool IsSub { get; set; } = true; - public string Flow { get; set; } public string Sni { get; set; } public string Alpn { get; set; } = string.Empty; public ECoreType? CoreType { get; set; } @@ -164,4 +178,21 @@ public class ProfileItem : ReactiveObject public string CertSha { get; set; } public string EchConfigList { get; set; } public string EchForceQuery { get; set; } + + public string ProtoExtra { get; set; } + + [Obsolete("Use ProtocolExtraItem.Ports instead.")] + public string Ports { get; set; } + + [Obsolete("Use ProtocolExtraItem.AlterId instead.")] + public int AlterId { get; set; } + + [Obsolete("Use ProtocolExtraItem.Flow instead.")] + public string Flow { get; set; } + + [Obsolete("Use ProfileItem.Password instead.")] + public string Id { get; set; } + + [Obsolete("Use ProtocolExtraItem.xxx instead.")] + public string Security { get; set; } } diff --git a/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs b/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs new file mode 100644 index 00000000..a768ba80 --- /dev/null +++ b/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs @@ -0,0 +1,38 @@ +namespace ServiceLib.Models; + +public record ProtocolExtraItem +{ + // vmess + public string? AlterId { get; init; } + public string? VmessSecurity { get; init; } + + // vless + public string? Flow { get; init; } + public string? VlessEncryption { get; init; } + //public string? VisionSeed { get; init; } + + // shadowsocks + //public string? PluginArgs { get; init; } + public string? SsMethod { get; init; } + + // wireguard + public string? WgPublicKey { get; init; } + public string? WgPresharedKey { get; init; } + public string? WgInterfaceAddress { get; init; } + public string? WgReserved { get; init; } + public int? WgMtu { get; init; } + + // hysteria2 + public string? SalamanderPass { get; init; } + public int? UpMbps { get; init; } + public int? DownMbps { get; init; } + public string? Ports { get; init; } + public string? HopInterval { get; init; } + + // group profile + public string? GroupType { get; init; } + public string? ChildItems { get; init; } + public string? SubChildItems { get; init; } + public string? Filter { get; init; } + public EMultipleLoad? MultipleLoad { get; init; } +} diff --git a/v2rayN/ServiceLib/Models/V2rayConfig.cs b/v2rayN/ServiceLib/Models/V2rayConfig.cs index c752168a..34456a8e 100644 --- a/v2rayN/ServiceLib/Models/V2rayConfig.cs +++ b/v2rayN/ServiceLib/Models/V2rayConfig.cs @@ -473,7 +473,7 @@ public class HysteriaSettings4Ray public class HysteriaUdpHop4Ray { public string? ports { get; set; } - public int? interval { get; set; } + public string? interval { get; set; } } public class FinalMask4Ray diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index fd3b2d9f..46de8983 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -2997,6 +2997,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Port hopping interval 的本地化字符串。 + /// + public static string TbHopInt7 { + get { + return ResourceManager.GetString("TbHopInt7", resourceCulture); + } + } + /// /// 查找类似 UUID(id) 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 16fe442f..fdcffe9b 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1665,4 +1665,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. + + Port hopping interval + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index a114ac21..5db657ca 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1662,4 +1662,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. + + Port hopping interval + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 21e4e68d..9e5c9c4d 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1665,4 +1665,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. + + Port hopping interval + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 91095de4..dfd744a3 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1665,4 +1665,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. + + Port hopping interval + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index adc9cc98..67cffea7 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1665,4 +1665,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. + + Port hopping interval + \ 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 b1675d48..f17495fd 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1662,4 +1662,7 @@ 当未选择或 "AsIs" 时,由远程服务器端 DNS 解析;否则,使用内部 DNS 模块解析。 + + 端口跳跃间隔 + \ 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 73a7dfe5..160340d1 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1662,4 +1662,7 @@ If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. + + Port hopping interval + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs index 7684cbd1..6e4cca95 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs @@ -242,7 +242,7 @@ public partial class CoreConfigSingboxService } else if (node?.ConfigType.IsGroupType() == true) { - var queryServerNames = (await ProfileGroupItemManager.GetAllChildEchQuerySni(node.IndexId)).ToList(); + var queryServerNames = (await GroupProfileManager.GetAllChildEchQuerySni(node)).ToList(); if (queryServerNames.Count > 0) { singboxConfig.dns.rules.Add(new() diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index e2c0d502..178350b3 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -6,6 +6,7 @@ public partial class CoreConfigSingboxService { try { + var protocolExtra = node.GetProtocolExtra(); outbound.server = node.Address; outbound.server_port = node.Port; outbound.type = Global.ProtocolTypes[node.ConfigType]; @@ -14,11 +15,11 @@ public partial class CoreConfigSingboxService { case EConfigType.VMess: { - outbound.uuid = node.Id; - outbound.alter_id = node.AlterId; - if (Global.VmessSecurities.Contains(node.Security)) + outbound.uuid = node.Password; + outbound.alter_id = int.TryParse(protocolExtra.AlterId, out var result) ? result : 0; + if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity)) { - outbound.security = node.Security; + outbound.security = protocolExtra.VmessSecurity; } else { @@ -31,8 +32,9 @@ public partial class CoreConfigSingboxService } case EConfigType.Shadowsocks: { - outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : Global.None; - outbound.password = node.Id; + outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod) + ? protocolExtra.SsMethod : Global.None; + outbound.password = node.Password; if (node.Network == nameof(ETransport.tcp) && node.HeaderType == Global.TcpHeaderHttp) { @@ -88,37 +90,37 @@ public partial class CoreConfigSingboxService case EConfigType.SOCKS: { outbound.version = "5"; - if (node.Security.IsNotEmpty() - && node.Id.IsNotEmpty()) + if (node.Username.IsNotEmpty() + && node.Password.IsNotEmpty()) { - outbound.username = node.Security; - outbound.password = node.Id; + outbound.username = node.Username; + outbound.password = node.Password; } break; } case EConfigType.HTTP: { - if (node.Security.IsNotEmpty() - && node.Id.IsNotEmpty()) + if (node.Username.IsNotEmpty() + && node.Password.IsNotEmpty()) { - outbound.username = node.Security; - outbound.password = node.Id; + outbound.username = node.Username; + outbound.password = node.Password; } break; } case EConfigType.VLESS: { - outbound.uuid = node.Id; + outbound.uuid = node.Password; outbound.packet_encoding = "xudp"; - if (node.Flow.IsNullOrEmpty()) + if (!protocolExtra.Flow.IsNullOrEmpty()) { - await GenOutboundMux(node, outbound); + outbound.flow = protocolExtra.Flow; } else { - outbound.flow = node.Flow; + await GenOutboundMux(node, outbound); } await GenOutboundTransport(node, outbound); @@ -126,7 +128,7 @@ public partial class CoreConfigSingboxService } case EConfigType.Trojan: { - outbound.password = node.Id; + outbound.password = node.Password; await GenOutboundMux(node, outbound); await GenOutboundTransport(node, outbound); @@ -134,23 +136,28 @@ public partial class CoreConfigSingboxService } case EConfigType.Hysteria2: { - outbound.password = node.Id; + outbound.password = node.Password; - if (node.Path.IsNotEmpty()) + if (!protocolExtra.SalamanderPass.IsNullOrEmpty()) { outbound.obfs = new() { type = "salamander", - password = node.Path.TrimEx(), + password = protocolExtra.SalamanderPass.TrimEx(), }; } - outbound.up_mbps = _config.HysteriaItem.UpMbps > 0 ? _config.HysteriaItem.UpMbps : null; - outbound.down_mbps = _config.HysteriaItem.DownMbps > 0 ? _config.HysteriaItem.DownMbps : null; - if (node.Ports.IsNotEmpty() && (node.Ports.Contains(':') || node.Ports.Contains('-') || node.Ports.Contains(','))) + outbound.up_mbps = protocolExtra?.UpMbps is { } su and >= 0 + ? su + : _config.HysteriaItem.UpMbps; + outbound.down_mbps = protocolExtra?.DownMbps is { } sd and >= 0 + ? sd + : _config.HysteriaItem.DownMbps; + var ports = protocolExtra?.Ports?.IsNullOrEmpty() == false ? protocolExtra.Ports : null; + if ((!ports.IsNullOrEmpty()) && (ports.Contains(':') || ports.Contains('-') || ports.Contains(','))) { outbound.server_port = null; - outbound.server_ports = node.Ports.Split(',') + outbound.server_ports = ports.Split(',') .Select(p => p.Trim()) .Where(p => p.IsNotEmpty()) .Select(p => @@ -159,21 +166,38 @@ public partial class CoreConfigSingboxService return port.Contains(':') ? port : $"{port}:{port}"; }) .ToList(); - outbound.hop_interval = _config.HysteriaItem.HopInterval > 0 ? $"{_config.HysteriaItem.HopInterval}s" : null; + outbound.hop_interval = _config.HysteriaItem.HopInterval >= 5 + ? $"{_config.HysteriaItem.HopInterval}s" + : $"{Global.Hysteria2DefaultHopInt}s"; + if (int.TryParse(protocolExtra.HopInterval, out var hiResult)) + { + outbound.hop_interval = hiResult >= 5 ? $"{hiResult}s" : outbound.hop_interval; + } + else if (protocolExtra.HopInterval?.Contains('-') ?? false) + { + // may be a range like 5-10 + var parts = protocolExtra.HopInterval.Split('-'); + if (parts.Length == 2 && int.TryParse(parts[0], out var hiL) && + int.TryParse(parts[0], out var hiH)) + { + var hi = (hiL + hiH) / 2; + outbound.hop_interval = hi >= 5 ? $"{hi}s" : outbound.hop_interval; + } + } } break; } case EConfigType.TUIC: { - outbound.uuid = node.Id; - outbound.password = node.Security; + outbound.uuid = node.Username; + outbound.password = node.Password; outbound.congestion_control = node.HeaderType; break; } case EConfigType.Anytls: { - outbound.password = node.Id; + outbound.password = node.Password; break; } } @@ -191,7 +215,9 @@ public partial class CoreConfigSingboxService { try { - endpoint.address = Utils.String2List(node.RequestHost); + var protocolExtra = node.GetProtocolExtra(); + + endpoint.address = Utils.String2List(protocolExtra.WgInterfaceAddress); endpoint.type = Global.ProtocolTypes[node.ConfigType]; switch (node.ConfigType) @@ -200,16 +226,17 @@ public partial class CoreConfigSingboxService { var peer = new Peer4Sbox { - public_key = node.PublicKey, - reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(), + public_key = protocolExtra.WgPublicKey, + pre_shared_key = protocolExtra.WgPresharedKey, + reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(), address = node.Address, port = node.Port, // TODO default ["0.0.0.0/0", "::/0"] allowed_ips = new() { "0.0.0.0/0", "::/0" }, }; - endpoint.private_key = node.Id; - endpoint.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(); - endpoint.peers = new() { peer }; + endpoint.private_key = node.Password; + endpoint.mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(); + endpoint.peers = [peer]; break; } } @@ -453,13 +480,13 @@ public partial class CoreConfigSingboxService { return -1; } - var hasCycle = ProfileGroupItemManager.HasCycle(node.IndexId); + var hasCycle = await GroupProfileManager.HasCycle(node); if (hasCycle) { return -1; } - var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); + var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node); if (childProfiles.Count <= 0) { return -1; @@ -467,13 +494,14 @@ public partial class CoreConfigSingboxService switch (node.ConfigType) { case EConfigType.PolicyGroup: + var multipleLoad = profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing; if (ignoreOriginChain) { - await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName); + await GenOutboundsList(childProfiles, singboxConfig, multipleLoad, baseTagName); } else { - await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName); + await GenOutboundsListWithChain(childProfiles, singboxConfig, multipleLoad, baseTagName); } break; @@ -585,7 +613,7 @@ public partial class CoreConfigSingboxService if (node.ConfigType.IsGroupType()) { - var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); + var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node); if (childProfiles.Count <= 0) { continue; @@ -594,7 +622,8 @@ public partial class CoreConfigSingboxService var ret = node.ConfigType switch { EConfigType.PolicyGroup => - await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName), + await GenOutboundsListWithChain(childProfiles, singboxConfig, + profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing, childBaseTagName), EConfigType.ProxyChain => await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName), _ => throw new NotImplementedException() @@ -763,7 +792,7 @@ public partial class CoreConfigSingboxService if (node.ConfigType.IsGroupType()) { - var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); + var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node); if (childProfiles.Count <= 0) { continue; @@ -772,7 +801,7 @@ public partial class CoreConfigSingboxService var ret = node.ConfigType switch { EConfigType.PolicyGroup => - await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName), + await GenOutboundsList(childProfiles, singboxConfig, profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing, childBaseTagName), EConfigType.ProxyChain => await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName), _ => throw new NotImplementedException() diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs index d3575685..7f9c26ca 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -6,6 +6,7 @@ public partial class CoreConfigV2rayService { try { + var protocolExtra = node.GetProtocolExtra(); var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; switch (node.ConfigType) { @@ -35,12 +36,12 @@ public partial class CoreConfigV2rayService usersItem = vnextItem.users.First(); } - usersItem.id = node.Id; - usersItem.alterId = node.AlterId; + usersItem.id = node.Password; + usersItem.alterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0; usersItem.email = Global.UserEMail; - if (Global.VmessSecurities.Contains(node.Security)) + if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity)) { - usersItem.security = node.Security; + usersItem.security = protocolExtra.VmessSecurity; } else { @@ -66,8 +67,9 @@ public partial class CoreConfigV2rayService } serversItem.address = node.Address; serversItem.port = node.Port; - serversItem.password = node.Id; - serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : "none"; + serversItem.password = node.Password; + serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod) + ? protocolExtra.SsMethod : "none"; serversItem.ota = false; serversItem.level = 1; @@ -95,13 +97,13 @@ public partial class CoreConfigV2rayService serversItem.method = null; serversItem.password = null; - if (node.Security.IsNotEmpty() - && node.Id.IsNotEmpty()) + if (node.Username.IsNotEmpty() + && node.Password.IsNotEmpty()) { SocksUsersItem4Ray socksUsersItem = new() { - user = node.Security, - pass = node.Id, + user = node.Username ?? "", + pass = node.Password, level = 1 }; @@ -138,17 +140,16 @@ public partial class CoreConfigV2rayService { usersItem = vnextItem.users.First(); } - usersItem.id = node.Id; + usersItem.id = node.Password; usersItem.email = Global.UserEMail; - usersItem.encryption = node.Security; + usersItem.encryption = protocolExtra.VlessEncryption; - if (node.Flow.IsNullOrEmpty()) + if (!protocolExtra.Flow.IsNullOrEmpty()) { - await GenOutboundMux(node, outbound, muxEnabled, muxEnabled); + usersItem.flow = protocolExtra.Flow; } else { - usersItem.flow = node.Flow; await GenOutboundMux(node, outbound, false, muxEnabled); } outbound.settings.servers = null; @@ -168,7 +169,7 @@ public partial class CoreConfigV2rayService } serversItem.address = node.Address; serversItem.port = node.Port; - serversItem.password = node.Id; + serversItem.password = node.Password; serversItem.ota = false; serversItem.level = 1; @@ -199,16 +200,16 @@ public partial class CoreConfigV2rayService } var peer = new WireguardPeer4Ray { - publicKey = node.PublicKey, + publicKey = protocolExtra.WgPublicKey ?? "", endpoint = address + ":" + node.Port.ToString() }; var setting = new Outboundsettings4Ray { - address = Utils.String2List(node.RequestHost), - secretKey = node.Id, - reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(), - mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(), - peers = new List { peer } + address = Utils.String2List(protocolExtra.WgInterfaceAddress), + secretKey = node.Password, + reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(), + mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(), + peers = [peer] }; outbound.settings = setting; outbound.settings.vnext = null; @@ -509,28 +510,38 @@ public partial class CoreConfigV2rayService break; case "hysteria": + var protocolExtra = node.GetProtocolExtra(); + var ports = protocolExtra?.Ports; + int? upMbps = protocolExtra?.UpMbps is { } su and >= 0 + ? su + : _config.HysteriaItem.UpMbps; + int? downMbps = protocolExtra?.DownMbps is { } sd and >= 0 + ? sd + : _config.HysteriaItem.UpMbps; + var hopInterval = !protocolExtra.HopInterval.IsNullOrEmpty() + ? protocolExtra.HopInterval + : (_config.HysteriaItem.HopInterval >= 5 + ? _config.HysteriaItem.HopInterval + : Global.Hysteria2DefaultHopInt).ToString(); HysteriaUdpHop4Ray? udpHop = null; - if (node.Ports.IsNotEmpty() && - (node.Ports.Contains(':') || node.Ports.Contains('-') || node.Ports.Contains(','))) + if (!ports.IsNullOrEmpty() && + (ports.Contains(':') || ports.Contains('-') || ports.Contains(','))) { - udpHop = new() + udpHop = new HysteriaUdpHop4Ray { - ports = node.Ports.Replace(':', '-'), - interval = _config.HysteriaItem.HopInterval > 0 - ? _config.HysteriaItem.HopInterval - : null, + ports = ports.Replace(':', '-'), + interval = hopInterval, }; } - HysteriaSettings4Ray hysteriaSettings = new() + streamSettings.hysteriaSettings = new() { version = 2, - auth = node.Id, - up = _config.HysteriaItem.UpMbps > 0 ? $"{_config.HysteriaItem.UpMbps}mbps" : null, - down = _config.HysteriaItem.DownMbps > 0 ? $"{_config.HysteriaItem.DownMbps}mbps" : null, + auth = node.Password, + up = upMbps > 0 ? $"{upMbps}mbps" : null, + down = downMbps > 0 ? $"{downMbps}mbps" : null, udphop = udpHop, }; - streamSettings.hysteriaSettings = hysteriaSettings; - if (node.Path.IsNotEmpty()) + if (!protocolExtra.SalamanderPass.IsNullOrEmpty()) { streamSettings.finalmask ??= new(); streamSettings.finalmask.udp = @@ -538,7 +549,7 @@ public partial class CoreConfigV2rayService new Mask4Ray { type = "salamander", - settings = new MaskSettings4Ray { password = node.Path.TrimEx(), } + settings = new MaskSettings4Ray { password = protocolExtra.SalamanderPass.TrimEx(), } } ]; } @@ -592,13 +603,13 @@ public partial class CoreConfigV2rayService { return -1; } - var hasCycle = ProfileGroupItemManager.HasCycle(node.IndexId); + var hasCycle = await GroupProfileManager.HasCycle(node); if (hasCycle) { return -1; } - var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); + var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node); if (childProfiles.Count <= 0) { return -1; @@ -627,8 +638,9 @@ public partial class CoreConfigV2rayService //add balancers if (node.ConfigType == EConfigType.PolicyGroup) { - await GenObservatory(v2rayConfig, profileGroupItem.MultipleLoad, baseTagName); - await GenBalancer(v2rayConfig, profileGroupItem.MultipleLoad, baseTagName); + var multipleLoad = profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing; + await GenObservatory(v2rayConfig, multipleLoad, baseTagName); + await GenBalancer(v2rayConfig, multipleLoad, baseTagName); } } catch (Exception ex) @@ -737,7 +749,7 @@ public partial class CoreConfigV2rayService if (node.ConfigType.IsGroupType()) { - var (childProfiles, _) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); + var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node); if (childProfiles.Count <= 0) { continue; @@ -891,7 +903,7 @@ public partial class CoreConfigV2rayService if (node.ConfigType.IsGroupType()) { - var (childProfiles, _) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId); + var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node); if (childProfiles.Count <= 0) { continue; diff --git a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs index 5b0778a5..c19d218b 100644 --- a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs @@ -79,8 +79,8 @@ public class AddGroupServerViewModel : MyReactiveObject public async Task Init() { - ProfileGroupItemManager.Instance.TryGet(SelectedSource.IndexId, out var profileGroup); - PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch + var protocolExtra = SelectedSource.GetProtocolExtra(); + PolicyGroupType = (protocolExtra?.MultipleLoad ?? EMultipleLoad.LeastPing) switch { EMultipleLoad.LeastPing => ResUI.TbLeastPing, EMultipleLoad.Fallback => ResUI.TbFallback, @@ -93,22 +93,18 @@ public class AddGroupServerViewModel : MyReactiveObject var subs = await AppManager.Instance.SubItems(); subs.Add(new SubItem()); SubItems.AddRange(subs); - SelectedSubItem = SubItems.Where(s => s.Id == profileGroup?.SubChildItems).FirstOrDefault(); - Filter = profileGroup?.Filter; + SelectedSubItem = SubItems.FirstOrDefault(s => s.Id == protocolExtra?.SubChildItems); + Filter = protocolExtra?.Filter; - var childItemMulti = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource?.IndexId); - if (childItemMulti != null) + var childIndexIds = Utils.String2List(protocolExtra?.ChildItems) ?? []; + foreach (var item in childIndexIds) { - var childIndexIds = Utils.String2List(childItemMulti.ChildItems) ?? []; - foreach (var item in childIndexIds) + var child = await AppManager.Instance.GetProfileItem(item); + if (child == null) { - var child = await AppManager.Instance.GetProfileItem(item); - if (child == null) - { - continue; - } - ChildItemsObs.Add(child); + continue; } + ChildItemsObs.Add(child); } } @@ -205,38 +201,32 @@ public class AddGroupServerViewModel : MyReactiveObject { return; } - var childIndexIds = new List(); - foreach (var item in ChildItemsObs) + + SelectedSource.SetProtocolExtra(SelectedSource.GetProtocolExtra() with { - if (item.IndexId.IsNullOrEmpty()) + ChildItems = + Utils.List2String(ChildItemsObs.Where(s => !s.IndexId.IsNullOrEmpty()).Select(s => s.IndexId).ToList()), + MultipleLoad = PolicyGroupType switch { - continue; - } - childIndexIds.Add(item.IndexId); - } - var profileGroup = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource.IndexId); - profileGroup.ChildItems = Utils.List2String(childIndexIds); - profileGroup.MultipleLoad = PolicyGroupType switch - { - var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing, - var s when s == ResUI.TbFallback => EMultipleLoad.Fallback, - var s when s == ResUI.TbRandom => EMultipleLoad.Random, - var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin, - var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad, - _ => EMultipleLoad.LeastPing, - }; + var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing, + var s when s == ResUI.TbFallback => EMultipleLoad.Fallback, + var s when s == ResUI.TbRandom => EMultipleLoad.Random, + var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin, + var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad, + _ => EMultipleLoad.LeastPing, + }, + SubChildItems = SelectedSubItem?.Id, + Filter = Filter, + }); - profileGroup.SubChildItems = SelectedSubItem?.Id; - profileGroup.Filter = Filter; - - var hasCycle = ProfileGroupItemManager.HasCycle(profileGroup.IndexId); + var hasCycle = await GroupProfileManager.HasCycle(SelectedSource.IndexId, SelectedSource.GetProtocolExtra()); if (hasCycle) { NoticeManager.Instance.Enqueue(string.Format(ResUI.GroupSelfReference, remarks)); return; } - if (await ConfigHandler.AddGroupServerCommon(_config, SelectedSource, profileGroup, true) == 0) + if (await ConfigHandler.AddServerCommon(_config, SelectedSource) == 0) { NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); _updateView?.Invoke(EViewAction.CloseWindow, null); diff --git a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs index 775274d7..914705c8 100644 --- a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs @@ -17,6 +17,47 @@ public class AddServerViewModel : MyReactiveObject [Reactive] public string CertSha { get; set; } + [Reactive] + public string SalamanderPass { get; set; } + + [Reactive] + public int AlterId { get; set; } + + [Reactive] + public string Ports { get; set; } + + [Reactive] + public int UpMbps { get; set; } + + [Reactive] + public int DownMbps { get; set; } + + [Reactive] + public string HopInterval { get; set; } + + [Reactive] + public string Flow { get; set; } + + [Reactive] + public string VmessSecurity { get; set; } + + [Reactive] + public string VlessEncryption { get; set; } + + [Reactive] + public string SsMethod { get; set; } + + [Reactive] + public string WgPublicKey { get; set; } + //[Reactive] + //public string WgPresharedKey { get; set; } + [Reactive] + public string WgInterfaceAddress { get; set; } + [Reactive] + public string WgReserved { get; set; } + [Reactive] + public int WgMtu { get; set; } + public ReactiveCommand FetchCertCmd { get; } public ReactiveCommand FetchCertChainCmd { get; } public ReactiveCommand SaveCmd { get; } @@ -63,6 +104,22 @@ public class AddServerViewModel : MyReactiveObject CoreType = SelectedSource?.CoreType?.ToString(); Cert = SelectedSource?.Cert?.ToString() ?? string.Empty; CertSha = SelectedSource?.CertSha?.ToString() ?? string.Empty; + + var protocolExtra = SelectedSource?.GetProtocolExtra(); + Ports = protocolExtra?.Ports ?? string.Empty; + AlterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0; + Flow = protocolExtra?.Flow ?? string.Empty; + SalamanderPass = protocolExtra?.SalamanderPass ?? string.Empty; + UpMbps = protocolExtra?.UpMbps ?? _config.HysteriaItem.UpMbps; + DownMbps = protocolExtra?.DownMbps ?? _config.HysteriaItem.DownMbps; + HopInterval = protocolExtra?.HopInterval.IsNullOrEmpty() ?? true ? Global.Hysteria2DefaultHopInt.ToString() : protocolExtra.HopInterval; + VmessSecurity = protocolExtra?.VmessSecurity?.IsNullOrEmpty() == false ? protocolExtra.VmessSecurity : Global.DefaultSecurity; + VlessEncryption = protocolExtra?.VlessEncryption.IsNullOrEmpty() == false ? protocolExtra.VlessEncryption : Global.None; + SsMethod = protocolExtra?.SsMethod ?? string.Empty; + WgPublicKey = protocolExtra?.WgPublicKey ?? string.Empty; + WgInterfaceAddress = protocolExtra?.WgInterfaceAddress ?? string.Empty; + WgReserved = protocolExtra?.WgReserved ?? string.Empty; + WgMtu = protocolExtra?.WgMtu ?? 1280; } private async Task SaveServerAsync() @@ -87,12 +144,12 @@ public class AddServerViewModel : MyReactiveObject } if (SelectedSource.ConfigType == EConfigType.Shadowsocks) { - if (SelectedSource.Id.IsNullOrEmpty()) + if (SelectedSource.Password.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.FillPassword); return; } - if (SelectedSource.Security.IsNullOrEmpty()) + if (SsMethod.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.PleaseSelectEncryption); return; @@ -100,7 +157,7 @@ public class AddServerViewModel : MyReactiveObject } if (SelectedSource.ConfigType is not EConfigType.SOCKS and not EConfigType.HTTP) { - if (SelectedSource.Id.IsNullOrEmpty()) + if (SelectedSource.Password.IsNullOrEmpty()) { NoticeManager.Instance.Enqueue(ResUI.FillUUID); return; @@ -109,6 +166,23 @@ public class AddServerViewModel : MyReactiveObject SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType); SelectedSource.Cert = Cert.IsNullOrEmpty() ? string.Empty : Cert; SelectedSource.CertSha = CertSha.IsNullOrEmpty() ? string.Empty : CertSha; + SelectedSource.SetProtocolExtra(SelectedSource.GetProtocolExtra() with + { + Ports = Ports.NullIfEmpty(), + AlterId = AlterId > 0 ? AlterId.ToString() : null, + Flow = Flow.NullIfEmpty(), + SalamanderPass = SalamanderPass.NullIfEmpty(), + UpMbps = UpMbps >= 0 ? UpMbps : null, + DownMbps = DownMbps >= 0 ? DownMbps : null, + HopInterval = HopInterval.NullIfEmpty(), + VmessSecurity = VmessSecurity.NullIfEmpty(), + VlessEncryption = VlessEncryption.NullIfEmpty(), + SsMethod = SsMethod.NullIfEmpty(), + WgPublicKey = WgPublicKey.NullIfEmpty(), + WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(), + WgReserved = WgReserved.NullIfEmpty(), + WgMtu = WgMtu >= 576 ? WgMtu : null, + }); if (await ConfigHandler.AddServer(_config, SelectedSource) == 0) { diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index a09ad4de..8f5e9029 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -259,7 +259,6 @@ public class MainWindowViewModel : MyReactiveObject await ConfigHandler.InitBuiltinDNS(_config); await ConfigHandler.InitBuiltinFullConfigTemplate(_config); await ProfileExManager.Instance.Init(); - await ProfileGroupItemManager.Instance.Init(); await CoreManager.Instance.Init(_config, UpdateHandler); TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler); diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs index 7301882c..ca065d5d 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs @@ -209,7 +209,7 @@ public class ProfilesSelectViewModel : MyReactiveObject Remarks = t.Remarks, Address = t.Address, Port = t.Port, - Security = t.Security, + //Security = t.Security, Network = t.Network, StreamSecurity = t.StreamSecurity, Subid = t.Subid, diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 0e141b74..7bd4cb98 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -446,7 +446,7 @@ public class ProfilesViewModel : MyReactiveObject Remarks = t.Remarks, Address = t.Address, Port = t.Port, - Security = t.Security, + //Security = t.Security, Network = t.Network, StreamSecurity = t.StreamSecurity, Subid = t.Subid, diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index 19c62868..356698fb 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -360,7 +360,7 @@ Grid.Row="2" ColumnDefinitions="300,Auto,Auto" IsVisible="False" - RowDefinitions="Auto,Auto,Auto,Auto"> + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto"> + + + + + + + + + case EConfigType.VMess: gridVMess.IsVisible = true; cmbSecurity.ItemsSource = Global.VmessSecurities; - if (profileItem.Security.IsNullOrEmpty()) - { - profileItem.Security = Global.DefaultSecurity; - } break; case EConfigType.Shadowsocks: @@ -59,10 +55,6 @@ public partial class AddServerWindow : WindowBase gridVLESS.IsVisible = true; lstStreamSecurity.Add(Global.StreamSecurityReality); cmbFlow5.ItemsSource = Global.Flows; - if (profileItem.Security.IsNullOrEmpty()) - { - profileItem.Security = Global.None; - } break; case EConfigType.Trojan: @@ -119,59 +111,62 @@ public partial class AddServerWindow : WindowBase switch (profileItem.ConfigType) { case EConfigType.VMess: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.VmessSecurity, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables); break; case EConfigType.Shadowsocks: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId3.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables); break; case EConfigType.SOCKS: case EConfigType.HTTP: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId4.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity4.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId4.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtSecurity4.Text).DisposeWith(disposables); break; case EConfigType.VLESS: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId5.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity5.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId5.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.VlessEncryption, v => v.txtSecurity5.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables); break; case EConfigType.Trojan: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId6.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId6.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables); break; case EConfigType.Hysteria2: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId7.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath7.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Ports, v => v.txtPorts7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SalamanderPass, v => v.txtPath7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Ports, v => v.txtPorts7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.HopInterval, v => v.txtHopInt7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.UpMbps, v => v.txtUpMbps7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.DownMbps, v => v.txtDownMbps7.Text).DisposeWith(disposables); break; case EConfigType.TUIC: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId8.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity8.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables); break; case EConfigType.WireGuard: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId9.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.PublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath9.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId9.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.WgPublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.WgReserved, v => v.txtPath9.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.WgInterfaceAddress, v => v.txtRequestHost9.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.WgMtu, v => v.txtShortId9.Text).DisposeWith(disposables); break; case EConfigType.Anytls: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId10.Text).DisposeWith(disposables); break; } this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayN/v2rayN/Views/AddServerWindow.xaml index 1f70489b..8c6b4a35 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -487,6 +487,8 @@ + + @@ -547,6 +549,47 @@ VerticalAlignment="Center" Style="{StaticResource ToolbarTextBlock}" Text="{x:Static resx:ResUI.TbPorts7Tips}" /> + + + + + + + + + vm.SelectedSource.Id, v => v.txtId.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.VmessSecurity, v => v.cmbSecurity.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables); break; case EConfigType.Shadowsocks: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId3.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity3.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables); break; case EConfigType.SOCKS: case EConfigType.HTTP: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId4.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity4.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId4.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtSecurity4.Text).DisposeWith(disposables); break; case EConfigType.VLESS: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId5.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow5.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity5.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId5.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow5.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.VlessEncryption, v => v.txtSecurity5.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables); break; case EConfigType.Trojan: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId6.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow6.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId6.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow6.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables); break; case EConfigType.Hysteria2: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId7.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath7.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Ports, v => v.txtPorts7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SalamanderPass, v => v.txtPath7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Ports, v => v.txtPorts7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.HopInterval, v => v.txtHopInt7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.UpMbps, v => v.txtUpMbps7.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.DownMbps, v => v.txtDownMbps7.Text).DisposeWith(disposables); break; case EConfigType.TUIC: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId8.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity8.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType8.Text).DisposeWith(disposables); break; case EConfigType.WireGuard: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId9.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.PublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath9.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId9.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.WgPublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.WgReserved, v => v.txtPath9.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.WgInterfaceAddress, v => v.txtRequestHost9.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.WgMtu, v => v.txtShortId9.Text).DisposeWith(disposables); break; case EConfigType.Anytls: - this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId10.Text).DisposeWith(disposables); break; } this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.Text).DisposeWith(disposables);