mirror of
https://github.com/2dust/v2rayN.git
synced 2025-07-01 12:32:10 +00:00
Compare commits
3 commits
3facccfb74
...
a664ce71b3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a664ce71b3 | ||
![]() |
84f93f2ae6 | ||
![]() |
d10c9da7c3 |
12 changed files with 805 additions and 732 deletions
919
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
919
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
File diff suppressed because it is too large
Load diff
|
@ -1131,6 +1131,15 @@
|
|||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>فعال کردن فرگمنت</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Fragment Interval</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Fragment Length</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Fragment Packets</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>فعال کردن کش فایل مجموعه قوانین برای sing-box</value>
|
||||
</data>
|
||||
|
@ -1419,4 +1428,4 @@
|
|||
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
|
||||
<value>URL آزمایش اطلاعات اتصال فعلی</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
|
@ -1131,6 +1131,15 @@
|
|||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Fragmentum engedélyezése</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Fragment Interval</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Fragment Length</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Fragment Packets</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Cache fájl engedélyezése a sing-box számára (szabálykészlet fájlok)</value>
|
||||
</data>
|
||||
|
|
|
@ -1131,6 +1131,15 @@
|
|||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Enable fragment</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Fragment Interval</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Fragment Length</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Fragment Packets</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Enable cache file for sing-box (ruleset files)</value>
|
||||
</data>
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
@ -816,9 +816,6 @@
|
|||
<data name="menuMoveUp" xml:space="preserve">
|
||||
<value>Вверх (U)</value>
|
||||
</data>
|
||||
<data name="menuMoveTo" xml:space="preserve">
|
||||
<value>Переместить вверх/вниз</value>
|
||||
</data>
|
||||
<data name="MsgFilterTitle" xml:space="preserve">
|
||||
<value>Фильтр, поддерживает regex</value>
|
||||
</data>
|
||||
|
@ -990,6 +987,9 @@
|
|||
<data name="TbSettingsSpeedTestUrl" xml:space="preserve">
|
||||
<value>URL для тестирования скорости</value>
|
||||
</data>
|
||||
<data name="menuMoveTo" xml:space="preserve">
|
||||
<value>Переместить вверх/вниз</value>
|
||||
</data>
|
||||
<data name="TbPublicKey" xml:space="preserve">
|
||||
<value>PublicKey</value>
|
||||
</data>
|
||||
|
@ -1131,6 +1131,15 @@
|
|||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>Включить фрагментацию (Fragment)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Fragment Interval</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Fragment Length</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Fragment Packets</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>Включить файл кэша для sing-box (файлы наборов правил)</value>
|
||||
</data>
|
||||
|
@ -1419,4 +1428,4 @@
|
|||
<data name="TbSettingsIPAPIUrl" xml:space="preserve">
|
||||
<value>URL для тестирования текущего соединения</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
|
@ -1128,6 +1128,15 @@
|
|||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>启用分片(Fragment)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Fragment Interval</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Fragment Length</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Fragment Packets</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>启用 sing-box(规则集文件)的缓存文件</value>
|
||||
</data>
|
||||
|
|
|
@ -1128,6 +1128,15 @@
|
|||
<data name="TbSettingsEnableFragment" xml:space="preserve">
|
||||
<value>啟用分片(Fragment)</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentInterval" xml:space="preserve">
|
||||
<value>Fragment Interval</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentLength" xml:space="preserve">
|
||||
<value>Fragment Length</value>
|
||||
</data>
|
||||
<data name="TbSettingsFragmentPackets" xml:space="preserve">
|
||||
<value>Fragment Packets</value>
|
||||
</data>
|
||||
<data name="TbSettingsEnableCacheFile4Sbox" xml:space="preserve">
|
||||
<value>啟用 sing-box(規則集檔案)的快取檔案</value>
|
||||
</data>
|
||||
|
|
|
@ -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<Outbound4Sbox>(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<Outbound4Sbox>(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,8 @@ public class CoreConfigSingboxService
|
|||
var proxyTags = new List<string>(); // For selector and urltest outbounds
|
||||
|
||||
// Cache for chain proxies to avoid duplicate generation
|
||||
var chainProxyCache = new Dictionary<string, (string?, Outbound4Sbox?)>();
|
||||
var prevProxyTags = new Dictionary<string, string>(); // Map from profile name to tag
|
||||
var nextProxyCache = new Dictionary<string, Outbound4Sbox?>();
|
||||
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
|
||||
int prevIndex = 0; // Index for prev outbounds
|
||||
|
||||
// Process each node
|
||||
|
@ -977,112 +969,55 @@ public class CoreConfigSingboxService
|
|||
{
|
||||
index++;
|
||||
|
||||
// Skip unsupported config types
|
||||
if (node.ConfigType is EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle proxy chain
|
||||
string? prevTag = null;
|
||||
Outbound4Sbox? nextOutbound = null;
|
||||
|
||||
if (node.Subid.IsNotEmpty())
|
||||
var currentOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||
var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null);
|
||||
if (nextOutbound != null)
|
||||
{
|
||||
// Check if chain proxy is already cached
|
||||
if (chainProxyCache.TryGetValue(node.Subid, out var chainProxy))
|
||||
nextOutbound = JsonUtils.DeepCopy(nextOutbound);
|
||||
}
|
||||
|
||||
var subItem = await AppHandler.Instance.GetSubItem(node.Subid);
|
||||
|
||||
// current proxy
|
||||
await GenOutbound(node, currentOutbound);
|
||||
currentOutbound.tag = $"{Global.ProxyTag}-{index}";
|
||||
proxyTags.Add(currentOutbound.tag);
|
||||
|
||||
if (!node.Subid.IsNullOrEmpty())
|
||||
{
|
||||
if (prevProxyTags.TryGetValue(node.Subid, out var value))
|
||||
{
|
||||
prevTag = chainProxy.Item1;
|
||||
nextOutbound = chainProxy.Item2;
|
||||
prevTag = value; // 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<Outbound4Sbox>(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<Outbound4Sbox>(txtOutbound);
|
||||
await GenOutbound(nextNode, nextOutbound);
|
||||
}
|
||||
|
||||
// Cache the chain proxy
|
||||
chainProxyCache[node.Subid] = (prevTag, nextOutbound);
|
||||
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||
await GenOutbound(prevNode, prevOutbound);
|
||||
prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}";
|
||||
prevOutbound.tag = prevTag;
|
||||
prevOutbounds.Add(prevOutbound);
|
||||
}
|
||||
prevProxyTags[node.Subid] = prevTag;
|
||||
}
|
||||
}
|
||||
|
||||
// Create main outbound
|
||||
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||
|
||||
await GenOutbound(node, outbound);
|
||||
outbound.tag = $"{Global.ProxyTag}-{index}";
|
||||
|
||||
// Configure proxy chain relationships
|
||||
if (nextOutbound != 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)
|
||||
nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound);
|
||||
if (!nextProxyCache.ContainsKey(node.Subid))
|
||||
{
|
||||
outbound.detour = prevTag;
|
||||
nextProxyCache[node.Subid] = nextOutbound;
|
||||
}
|
||||
|
||||
// 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 (nextOutbound is not null)
|
||||
{
|
||||
// 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 +1059,53 @@ public class CoreConfigSingboxService
|
|||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="subItem">The subscription item containing proxy chain information.</param>
|
||||
/// <param name="outbound">The current outbound configuration. Its tag must be set before calling this method.</param>
|
||||
/// <param name="prevOutboundTag">The tag of the previous outbound in the chain, if any.</param>
|
||||
/// <param name="nextOutbound">The outbound for the next proxy in the chain, if already created. If null, will be created inside.</param>
|
||||
/// <returns>
|
||||
/// The outbound configuration for the next proxy in the chain, or null if no next proxy exists.
|
||||
/// </returns>
|
||||
private async Task<Outbound4Sbox?> GenChainOutbounds(SubItem subItem, Outbound4Sbox outbound, string? prevOutboundTag, Outbound4Sbox? nextOutbound = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||
|
||||
if (!prevOutboundTag.IsNullOrEmpty())
|
||||
{
|
||||
outbound.detour = prevOutboundTag;
|
||||
}
|
||||
|
||||
// Next proxy
|
||||
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||
if (nextNode is not null
|
||||
&& nextNode.ConfigType != EConfigType.Custom)
|
||||
{
|
||||
if (nextOutbound == null)
|
||||
{
|
||||
nextOutbound = JsonUtils.Deserialize<Outbound4Sbox>(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<int> GenRouting(SingboxConfig singboxConfig)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -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<Outbounds4Ray>(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<Outbounds4Ray>(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,9 @@ public class CoreConfigV2rayService
|
|||
var resultOutbounds = new List<Outbounds4Ray>();
|
||||
var prevOutbounds = new List<Outbounds4Ray>(); // 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<string, (string?, Outbounds4Ray?)>();
|
||||
var prevProxyTags = new Dictionary<string, string>(); // Map from profile name to tag
|
||||
var nextProxyCache = new Dictionary<string, Outbounds4Ray?>();
|
||||
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
|
||||
int prevIndex = 0; // Index for prev outbounds
|
||||
|
||||
// Process nodes
|
||||
|
@ -1408,126 +1370,56 @@ 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;
|
||||
Outbounds4Ray? nextOutbound = null;
|
||||
var currentOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null);
|
||||
if (nextOutbound != null)
|
||||
{
|
||||
nextOutbound = JsonUtils.DeepCopy(nextOutbound);
|
||||
}
|
||||
|
||||
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.TryGetValue(node.Subid, out var value))
|
||||
{
|
||||
prevTag = chainProxy.Item1;
|
||||
nextOutbound = chainProxy.Item2;
|
||||
prevTag = value; // 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<Outbounds4Ray>(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<Outbounds4Ray>(txtOutbound);
|
||||
await GenOutbound(nextNode, nextOutbound);
|
||||
}
|
||||
|
||||
// Cache the chain proxy
|
||||
chainProxyCache[node.Subid] = (prevTag, nextOutbound);
|
||||
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(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, nextOutbound);
|
||||
if (!nextProxyCache.ContainsKey(node.Subid))
|
||||
{
|
||||
nextProxyCache[node.Subid] = nextOutbound;
|
||||
}
|
||||
}
|
||||
|
||||
// Create main outbound
|
||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(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 +1435,61 @@ public class CoreConfigV2rayService
|
|||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="subItem">The subscription item containing proxy chain information.</param>
|
||||
/// <param name="outbound">The current outbound configuration. Its tag must be set before calling this method.</param>
|
||||
/// <param name="prevOutboundTag">The tag of the previous outbound in the chain, if any.</param>
|
||||
/// <param name="nextOutbound">The outbound for the next proxy in the chain, if already created. If null, will be created inside.</param>
|
||||
/// <returns>
|
||||
/// The outbound configuration for the next proxy in the chain, or null if no next proxy exists.
|
||||
/// </returns>
|
||||
private async Task<Outbounds4Ray?> GenChainOutbounds(SubItem subItem, Outbounds4Ray outbound, string? prevOutboundTag, Outbounds4Ray? nextOutbound = null)
|
||||
{
|
||||
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);
|
||||
if (nextNode is not null
|
||||
&& nextNode.ConfigType != EConfigType.Custom
|
||||
&& nextNode.ConfigType != EConfigType.Hysteria2
|
||||
&& nextNode.ConfigType != EConfigType.TUIC)
|
||||
{
|
||||
if (nextOutbound == null)
|
||||
{
|
||||
nextOutbound = JsonUtils.Deserialize<Outbounds4Ray>(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<int> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad)
|
||||
{
|
||||
if (multipleLoad == EMultipleLoad.LeastPing)
|
||||
|
|
|
@ -29,6 +29,9 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||
[Reactive] public int hyUpMbps { get; set; }
|
||||
[Reactive] public int hyDownMbps { get; set; }
|
||||
[Reactive] public bool enableFragment { get; set; }
|
||||
[Reactive] public string fragmentPackets { get; set; }
|
||||
[Reactive] public string fragmentInterval { get; set; }
|
||||
[Reactive] public string fragmentLength { get; set; }
|
||||
|
||||
#endregion Core
|
||||
|
||||
|
@ -146,6 +149,9 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||
hyUpMbps = _config.HysteriaItem.UpMbps;
|
||||
hyDownMbps = _config.HysteriaItem.DownMbps;
|
||||
enableFragment = _config.CoreBasicItem.EnableFragment;
|
||||
fragmentInterval = _config.Fragment4RayItem.Interval;
|
||||
fragmentLength = _config.Fragment4RayItem.Length;
|
||||
fragmentPackets = _config.Fragment4RayItem.Packets;
|
||||
|
||||
#endregion Core
|
||||
|
||||
|
@ -321,6 +327,9 @@ public class OptionSettingViewModel : MyReactiveObject
|
|||
_config.HysteriaItem.UpMbps = hyUpMbps;
|
||||
_config.HysteriaItem.DownMbps = hyDownMbps;
|
||||
_config.CoreBasicItem.EnableFragment = enableFragment;
|
||||
_config.Fragment4RayItem.Packets = fragmentPackets;
|
||||
_config.Fragment4RayItem.Interval = fragmentInterval;
|
||||
_config.Fragment4RayItem.Length = fragmentLength;
|
||||
|
||||
_config.GuiItem.AutoRun = AutoRun;
|
||||
_config.GuiItem.EnableStatistics = EnableStatistics;
|
||||
|
|
|
@ -66,6 +66,9 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
@ -399,6 +402,54 @@
|
|||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsEnableFragmentTips}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="21"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentPackets}" />
|
||||
<TextBox
|
||||
x:Name="txtfragmentpackets"
|
||||
Grid.Row="21"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin8}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="22"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentLength}" />
|
||||
<TextBox
|
||||
x:Name="txtfragmentlength"
|
||||
Grid.Row="22"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin8}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="23"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsFragmentInterval}" />
|
||||
<TextBox
|
||||
x:Name="txtfragmentinterval"
|
||||
Grid.Row="23"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin8}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
|
|
@ -134,6 +134,9 @@ public partial class OptionSettingWindow
|
|||
this.Bind(ViewModel, vm => vm.hyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.hyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.enableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.fragmentInterval, v => v.txtfragmentinterval.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.fragmentLength, v => v.txtfragmentlength.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.fragmentPackets, v => v.txtfragmentpackets.Text).DisposeWith(disposables);
|
||||
|
||||
//this.Bind(ViewModel, vm => vm.Kcpmtu, v => v.txtKcpmtu.Text).DisposeWith(disposables);
|
||||
//this.Bind(ViewModel, vm => vm.Kcptti, v => v.txtKcptti.Text).DisposeWith(disposables);
|
||||
|
|
Loading…
Reference in a new issue