diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index cdc2d5f5..1c1d58a7 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -918,29 +918,21 @@ public class CoreConfigSingboxService //Previous proxy var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + string? prevOutboundTag = null; if (prevNode is not null && prevNode.ConfigType != EConfigType.Custom) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); - prevOutbound.tag = $"{Global.ProxyTag}2"; + prevOutboundTag = $"prev-{Global.ProxyTag}"; + prevOutbound.tag = prevOutboundTag; singboxConfig.outbounds.Add(prevOutbound); - - outbound.detour = prevOutbound.tag; } + var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag); - //Next proxy - var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom) + if (nextOutbound is not null) { - var nextOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(nextNode, nextOutbound); - nextOutbound.tag = Global.ProxyTag; singboxConfig.outbounds.Insert(0, nextOutbound); - - outbound.tag = $"{Global.ProxyTag}1"; - nextOutbound.detour = outbound.tag; } } catch (Exception ex) @@ -967,8 +959,7 @@ public class CoreConfigSingboxService var proxyTags = new List(); // For selector and urltest outbounds // Cache for chain proxies to avoid duplicate generation - var chainProxyCache = new Dictionary(); - var prevProxyTags = new Dictionary(); // Map from profile name to tag + var prevProxyTags = new Dictionary(); // Map from profile name to tag int prevIndex = 0; // Index for prev outbounds // Process each node @@ -977,112 +968,46 @@ public class CoreConfigSingboxService { index++; - // Skip unsupported config types - if (node.ConfigType is EConfigType.Custom) - { - continue; - } - // Handle proxy chain string? prevTag = null; + var currentOutbound = JsonUtils.Deserialize(txtOutbound); Outbound4Sbox? nextOutbound = null; - if (node.Subid.IsNotEmpty()) + var subItem = await AppHandler.Instance.GetSubItem(node.Subid); + + // current proxy + await GenOutbound(node, currentOutbound); + currentOutbound.tag = $"{Global.ProxyTag}-{index}"; + + if (!node.Subid.IsNullOrEmpty()) { - // Check if chain proxy is already cached - if (chainProxyCache.TryGetValue(node.Subid, out var chainProxy)) + if (prevProxyTags.ContainsKey(node.Subid)) { - prevTag = chainProxy.Item1; - nextOutbound = chainProxy.Item2; + prevTag = prevProxyTags[node.Subid]; // maybe null } else { - // Generate chain proxy and cache it - var subItem = await AppHandler.Instance.GetSubItem(node.Subid); - if (subItem != null) + var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + if (prevNode is not null + && prevNode.ConfigType != EConfigType.Custom) { - // Process previous proxy - if (!subItem.PrevProfile.IsNullOrEmpty()) - { - // Check if this previous proxy was already created - if (prevProxyTags.TryGetValue(subItem.PrevProfile, out var existingTag)) - { - prevTag = existingTag; - } - else - { - var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - if (prevNode != null && prevNode.ConfigType != EConfigType.Custom) - { - prevIndex++; - var prevOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(prevNode, prevOutbound); - - prevTag = $"{Global.ProxyTag}-prev-{prevIndex}"; - prevOutbound.tag = prevTag; - prevProxyTags[subItem.PrevProfile] = prevTag; - - // Add to prev outbounds list (will be added at the end) - prevOutbounds.Add(prevOutbound); - } - } - } - - // Process next proxy - var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode != null && nextNode.ConfigType != EConfigType.Custom) - { - nextOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(nextNode, nextOutbound); - } - - // Cache the chain proxy - chainProxyCache[node.Subid] = (prevTag, nextOutbound); + var prevOutbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(prevNode, prevOutbound); + prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}"; + prevOutbound.tag = prevTag; + prevOutbounds.Add(prevOutbound); } + prevProxyTags[node.Subid] = prevTag; } + + nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag); } - // Create main outbound - var outbound = JsonUtils.Deserialize(txtOutbound); - - await GenOutbound(node, outbound); - outbound.tag = $"{Global.ProxyTag}-{index}"; - - // Configure proxy chain relationships - if (nextOutbound != null) + if (nextOutbound is not null) { - // If there's a next proxy, it should be the final outbound in the chain - var originalTag = outbound.tag; - outbound.tag = $"mid-{Global.ProxyTag}-{index}"; - - var nextOutboundCopy = JsonUtils.DeepCopy(nextOutbound); - nextOutboundCopy.tag = originalTag; - nextOutboundCopy.detour = outbound.tag; // Use detour instead of sockopt - - if (prevTag != null) - { - outbound.detour = prevTag; - } - - // Add to proxy tags for selector/urltest - proxyTags.Add(originalTag); - - // Add in reverse order to ensure final outbound is added first - resultOutbounds.Add(nextOutboundCopy); // Final outbound (exposed to internet) - resultOutbounds.Add(outbound); // Middle outbound - } - else - { - // If no next proxy, the main outbound is the final one - if (prevTag != null) - { - outbound.detour = prevTag; - } - - // Add to proxy tags for selector/urltest - proxyTags.Add(outbound.tag); - resultOutbounds.Add(outbound); + resultOutbounds.Add(nextOutbound); } + resultOutbounds.Add(currentOutbound); } // Add urltest outbound (auto selection based on latency) @@ -1124,6 +1049,50 @@ public class CoreConfigSingboxService return 0; } + /// + /// Generates a chained outbound configuration for the given subItem and outbound. + /// The outbound's tag must be set before calling this method. + /// Returns the next proxy's outbound configuration, which may be null if no next proxy exists. + /// + /// The subscription item containing proxy chain information. + /// The current outbound configuration. Its tag must be set before calling this method. + /// The tag of the previous outbound in the chain, if any. + /// + /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. + /// + private async Task GenChainOutbounds(SubItem subItem, Outbound4Sbox outbound, string? prevOutboundTag) + { + try + { + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); + + if (!prevOutboundTag.IsNullOrEmpty()) + { + outbound.detour = prevOutboundTag; + } + + // Next proxy + var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); + Outbound4Sbox? nextOutbound = null; + if (nextNode is not null + && nextNode.ConfigType != EConfigType.Custom) + { + nextOutbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(nextNode, nextOutbound); + nextOutbound.tag = outbound.tag; + + outbound.tag = $"mid-{outbound.tag}"; + nextOutbound.detour = outbound.tag; + } + return nextOutbound; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return null; + } + private async Task GenRouting(SingboxConfig singboxConfig) { try diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index fc10021c..2db69ca4 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -1318,6 +1318,7 @@ public class CoreConfigV2rayService //Previous proxy var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + string? prevOutboundTag = null; if (prevNode is not null && prevNode.ConfigType != EConfigType.Custom && prevNode.ConfigType != EConfigType.Hysteria2 @@ -1325,32 +1326,15 @@ public class CoreConfigV2rayService { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); - prevOutbound.tag = $"{Global.ProxyTag}2"; + prevOutboundTag = $"prev-{Global.ProxyTag}"; + prevOutbound.tag = prevOutboundTag; v2rayConfig.outbounds.Add(prevOutbound); - - outbound.streamSettings.sockopt = new() - { - dialerProxy = prevOutbound.tag - }; } + var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag); - //Next proxy - var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode is not null - && nextNode.ConfigType != EConfigType.Custom - && nextNode.ConfigType != EConfigType.Hysteria2 - && nextNode.ConfigType != EConfigType.TUIC) + if (nextOutbound is not null) { - var nextOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(nextNode, nextOutbound); - nextOutbound.tag = Global.ProxyTag; v2rayConfig.outbounds.Insert(0, nextOutbound); - - outbound.tag = $"{Global.ProxyTag}1"; - nextOutbound.streamSettings.sockopt = new() - { - dialerProxy = outbound.tag - }; } } catch (Exception ex) @@ -1375,31 +1359,8 @@ public class CoreConfigV2rayService var resultOutbounds = new List(); var prevOutbounds = new List(); // Separate list for prev outbounds and fragment - // Handle fragment outbound - Outbounds4Ray? fragmentOutbound = null; - if (_config.CoreBasicItem.EnableFragment) - { - fragmentOutbound = new Outbounds4Ray - { - protocol = "freedom", - tag = $"fragment-{Global.ProxyTag}", - settings = new() - { - fragment = new() - { - packets = _config.Fragment4RayItem?.Packets, - length = _config.Fragment4RayItem?.Length, - interval = _config.Fragment4RayItem?.Interval - } - } - }; - // Add to prevOutbounds instead of v2rayConfig.outbounds - prevOutbounds.Add(fragmentOutbound); - } - // Cache for chain proxies to avoid duplicate generation - var chainProxyCache = new Dictionary(); - var prevProxyTags = new Dictionary(); // Map from profile name to tag + var prevProxyTags = new Dictionary(); // Map from profile name to tag int prevIndex = 0; // Index for prev outbounds // Process nodes @@ -1408,126 +1369,48 @@ public class CoreConfigV2rayService { index++; - // Skip unsupported config types - if (node.ConfigType is EConfigType.Custom or EConfigType.Hysteria2 or EConfigType.TUIC) - { - continue; - } - // Handle proxy chain string? prevTag = null; + var currentOutbound = JsonUtils.Deserialize(txtOutbound); Outbounds4Ray? nextOutbound = null; + var subItem = await AppHandler.Instance.GetSubItem(node.Subid); + + // current proxy + await GenOutbound(node, currentOutbound); + currentOutbound.tag = $"{Global.ProxyTag}-{index}"; + if (!node.Subid.IsNullOrEmpty()) { - // Check if chain proxy is already cached - if (chainProxyCache.TryGetValue(node.Subid, out var chainProxy)) + if (prevProxyTags.ContainsKey(node.Subid)) { - prevTag = chainProxy.Item1; - nextOutbound = chainProxy.Item2; + prevTag = prevProxyTags[node.Subid]; // maybe null } else { - // Generate chain proxy and cache it - var subItem = await AppHandler.Instance.GetSubItem(node.Subid); - if (subItem != null) + var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + if (prevNode is not null + && prevNode.ConfigType != EConfigType.Custom + && prevNode.ConfigType != EConfigType.Hysteria2 + && prevNode.ConfigType != EConfigType.TUIC) { - // Process previous proxy - if (!subItem.PrevProfile.IsNullOrEmpty()) - { - // Check if this previous proxy was already created - if (prevProxyTags.TryGetValue(subItem.PrevProfile, out var existingTag)) - { - prevTag = existingTag; - } - else - { - var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - if (prevNode != null - && prevNode.ConfigType != EConfigType.Custom - && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC) - { - prevIndex++; - var prevOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(prevNode, prevOutbound); - - prevTag = $"{Global.ProxyTag}-prev-{prevIndex}"; - prevOutbound.tag = prevTag; - prevProxyTags[subItem.PrevProfile] = prevTag; - - // Set fragment if needed - if (fragmentOutbound != null && prevOutbound.streamSettings?.security.IsNullOrEmpty() == false) - { - prevOutbound.streamSettings.sockopt = new() - { - dialerProxy = fragmentOutbound.tag - }; - } - - // Add to prev outbounds list (will be added at the end) - prevOutbounds.Add(prevOutbound); - } - } - } - - // Process next proxy - var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode != null - && nextNode.ConfigType != EConfigType.Custom - && nextNode.ConfigType != EConfigType.Hysteria2 - && nextNode.ConfigType != EConfigType.TUIC) - { - nextOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(nextNode, nextOutbound); - } - - // Cache the chain proxy - chainProxyCache[node.Subid] = (prevTag, nextOutbound); + var prevOutbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(prevNode, prevOutbound); + prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}"; + prevOutbound.tag = prevTag; + prevOutbounds.Add(prevOutbound); } + prevProxyTags[node.Subid] = prevTag; } + + nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag); } - // Create main outbound - var outbound = JsonUtils.Deserialize(txtOutbound); - - await GenOutbound(node, outbound); - outbound.tag = $"{Global.ProxyTag}-{index}"; - - // Configure proxy chain relationships - if (nextOutbound != null) + if (nextOutbound is not null) { - // If there's a next proxy, it should be the final outbound in the chain - var originalTag = outbound.tag; - outbound.tag = $"mid-{Global.ProxyTag}-{index}"; - - var nextOutboundCopy = JsonUtils.DeepCopy(nextOutbound); - nextOutboundCopy.tag = originalTag; - nextOutboundCopy.streamSettings.sockopt = new() { dialerProxy = outbound.tag }; - - if (prevTag != null) - { - outbound.streamSettings.sockopt = new() { dialerProxy = prevTag }; - } - - // Add in reverse order to ensure final outbound is added first - resultOutbounds.Add(nextOutboundCopy); // Final outbound (exposed to internet) - resultOutbounds.Add(outbound); // Middle outbound - } - else - { - // If no next proxy, the main outbound is the final one - if (prevTag != null) - { - outbound.streamSettings.sockopt = new() { dialerProxy = prevTag }; - } - else if (fragmentOutbound != null && outbound.streamSettings?.security.IsNullOrEmpty() == false) - { - outbound.streamSettings.sockopt = new() { dialerProxy = fragmentOutbound.tag }; - } - - resultOutbounds.Add(outbound); + resultOutbounds.Add(nextOutbound); } + resultOutbounds.Add(currentOutbound); } // Merge results: first the main chain outbounds, then other outbounds, and finally utility outbounds @@ -1543,6 +1426,58 @@ public class CoreConfigV2rayService return 0; } + /// + /// Generates a chained outbound configuration for the given subItem and outbound. + /// The outbound's tag must be set before calling this method. + /// Returns the next proxy's outbound configuration, which may be null if no next proxy exists. + /// + /// The subscription item containing proxy chain information. + /// The current outbound configuration. Its tag must be set before calling this method. + /// The tag of the previous outbound in the chain, if any. + /// + /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. + /// + private async Task GenChainOutbounds(SubItem subItem, Outbounds4Ray outbound, string? prevOutboundTag) + { + try + { + var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); + + if (!prevOutboundTag.IsNullOrEmpty()) + { + outbound.streamSettings.sockopt = new() + { + dialerProxy = prevOutboundTag + }; + } + + // Next proxy + var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); + Outbounds4Ray? nextOutbound = null; + if (nextNode is not null + && nextNode.ConfigType != EConfigType.Custom + && nextNode.ConfigType != EConfigType.Hysteria2 + && nextNode.ConfigType != EConfigType.TUIC) + { + nextOutbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(nextNode, nextOutbound); + nextOutbound.tag = outbound.tag; + + outbound.tag = $"mid-{outbound.tag}"; + nextOutbound.streamSettings.sockopt = new() + { + dialerProxy = outbound.tag + }; + } + return nextOutbound; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return null; + } + private async Task GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad) { if (multipleLoad == EMultipleLoad.LeastPing)