diff --git a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs
index 23a495f8..0139d3fe 100644
--- a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs
+++ b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs
@@ -5,6 +5,30 @@ public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeVali
public bool Success => ValidatorResult.Success;
}
+///
+/// Holds the results of a full context build, including the main context and an optional
+/// pre-socks context (e.g. for TUN protection or pre-socks chaining).
+///
+public record CoreConfigContextBuilderAllResult(
+ CoreConfigContextBuilderResult MainResult,
+ CoreConfigContextBuilderResult? PreSocksResult)
+{
+ /// True only when both the main result and (if present) the pre-socks result succeeded.
+ public bool Success => MainResult.Success && (PreSocksResult?.Success ?? true);
+
+ ///
+ /// The main context with TunProtectSsPort/ProxyRelaySsPort merged in from the
+ /// pre-socks result (if any). Pass this to the core runner.
+ ///
+ public CoreConfigContext ResolvedMainContext => PreSocksResult is not null
+ ? MainResult.Context with
+ {
+ TunProtectSsPort = PreSocksResult.Context.TunProtectSsPort,
+ ProxyRelaySsPort = PreSocksResult.Context.ProxyRelaySsPort,
+ }
+ : MainResult.Context;
+}
+
public class CoreConfigContextBuilder
{
///
@@ -75,6 +99,80 @@ public class CoreConfigContextBuilder
return new CoreConfigContextBuilderResult(context, validatorResult);
}
+ ///
+ /// Builds the main for and, when
+ /// the main build succeeds, also builds the optional pre-socks context required for TUN
+ /// protection or pre-socks proxy chaining.
+ ///
+ public static async Task BuildAll(Config config, ProfileItem node)
+ {
+ var mainResult = await Build(config, node);
+ if (!mainResult.Success)
+ {
+ return new CoreConfigContextBuilderAllResult(mainResult, null);
+ }
+
+ var preResult = await BuildPreSocksIfNeeded(mainResult.Context);
+ return new CoreConfigContextBuilderAllResult(mainResult, preResult);
+ }
+
+ ///
+ /// Determines whether a pre-socks context is required for
+ /// and, if so, builds and returns it. Returns null when no pre-socks core is needed.
+ ///
+ private static async Task BuildPreSocksIfNeeded(CoreConfigContext nodeContext)
+ {
+ var config = nodeContext.AppConfig;
+ var node = nodeContext.Node;
+ var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
+
+ var preSocksItem = ConfigHandler.GetPreSocksItem(config, node, coreType);
+ if (preSocksItem != null)
+ {
+ var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem);
+ var protectDomainList = nodeContext.ProtectDomainList ?? [];
+ protectDomainList.UnionWith(preSocksResult.Context.ProtectDomainList ?? []);
+ return preSocksResult with
+ {
+ Context = preSocksResult.Context with { ProtectDomainList = protectDomainList }
+ };
+ }
+
+ if (!nodeContext.IsTunEnabled
+ || coreType != ECoreType.Xray
+ || node.ConfigType == EConfigType.Custom)
+ {
+ return null;
+ }
+
+ var tunProtectSsPort = Utils.GetFreePort();
+ var proxyRelaySsPort = Utils.GetFreePort();
+ var preItem = new ProfileItem()
+ {
+ CoreType = ECoreType.sing_box,
+ ConfigType = EConfigType.Shadowsocks,
+ Address = Global.Loopback,
+ Port = proxyRelaySsPort,
+ Password = Global.None,
+ };
+ preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
+ {
+ SsMethod = Global.None,
+ });
+ var preResult2 = await Build(nodeContext.AppConfig, preItem);
+ var protectDomainList2 = nodeContext.ProtectDomainList ?? [];
+ protectDomainList2.UnionWith(preResult2.Context.ProtectDomainList ?? []);
+ return preResult2 with
+ {
+ Context = preResult2.Context with
+ {
+ ProtectDomainList = protectDomainList2,
+ TunProtectSsPort = tunProtectSsPort,
+ ProxyRelaySsPort = proxyRelaySsPort,
+ }
+ };
+ }
+
///
/// Resolves a node into the context, optionally wrapping it in a subscription-level proxy chain.
/// Returns the effective (possibly replaced) node and the validation result.
diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs
index faa35650..a3bd3f8c 100644
--- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs
+++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs
@@ -1251,62 +1251,6 @@ public static class ConfigHandler
return itemSocks;
}
- public static async Task GetPreSocksCoreConfigContext(CoreConfigContext nodeContext)
- {
- var config = nodeContext.AppConfig;
- var node = nodeContext.Node;
- var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
-
- var preSocksItem = GetPreSocksItem(config, node, coreType);
- if (preSocksItem != null)
- {
- var preSocksResult = await CoreConfigContextBuilder.Build(nodeContext.AppConfig, preSocksItem);
- // share protect domain
- var protectDomainList = nodeContext.ProtectDomainList ?? [];
- protectDomainList.UnionWith(preSocksResult.Context.ProtectDomainList ?? []);
- preSocksResult = preSocksResult with
- {
- Context = preSocksResult.Context with { ProtectDomainList = protectDomainList, }
- };
- return preSocksResult;
- }
-
- if ((!nodeContext.IsTunEnabled)
- || coreType != ECoreType.Xray
- || node.ConfigType == EConfigType.Custom)
- {
- return null;
- }
- var tunProtectSsPort = Utils.GetFreePort();
- var proxyRelaySsPort = Utils.GetFreePort();
- var preItem = new ProfileItem()
- {
- CoreType = ECoreType.sing_box,
- ConfigType = EConfigType.Shadowsocks,
- Address = Global.Loopback,
- Port = proxyRelaySsPort,
- Password = Global.None,
- };
- preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
- {
- SsMethod = Global.None,
- });
- var preResult = await CoreConfigContextBuilder.Build(nodeContext.AppConfig, preItem);
- // share protect domain
- var protectDomainList2 = nodeContext.ProtectDomainList ?? [];
- protectDomainList2.UnionWith(preResult.Context.ProtectDomainList ?? []);
- preResult = preResult with
- {
- Context = preResult.Context with
- {
- ProtectDomainList = protectDomainList2,
- TunProtectSsPort = tunProtectSsPort,
- ProxyRelaySsPort = proxyRelaySsPort,
- }
- };
- return preResult;
- }
-
///
/// Remove servers with invalid test results (timeout)
/// Useful for cleaning up subscription lists
diff --git a/v2rayN/ServiceLib/Manager/CoreManager.cs b/v2rayN/ServiceLib/Manager/CoreManager.cs
index 83355597..b2df4562 100644
--- a/v2rayN/ServiceLib/Manager/CoreManager.cs
+++ b/v2rayN/ServiceLib/Manager/CoreManager.cs
@@ -57,45 +57,19 @@ public class CoreManager
}
}
- public async Task LoadCore(CoreConfigContext? context)
+ /// Resolved main context (with pre-socks ports already merged if applicable).
+ /// Optional pre-socks context passed to .
+ public async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
{
- if (context == null)
+ if (mainContext == null)
{
await UpdateFunc(false, ResUI.CheckServerSettings);
return;
}
- var contextMod = context;
- var node = contextMod.Node;
+ var node = mainContext.Node;
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
- var preResult = await ConfigHandler.GetPreSocksCoreConfigContext(contextMod);
- if (preResult is not null)
- {
- var validatorResult = preResult.ValidatorResult;
- var msgs = new List([.. validatorResult.Errors, .. validatorResult.Warnings]);
- if (msgs.Count > 0)
- {
- foreach (var msg in msgs)
- {
- await UpdateFunc(false, msg);
- }
- await UpdateFunc(true, Utils.List2String(msgs.Take(10).ToList(), true));
- if (!validatorResult.Success)
- {
- return;
- }
- }
- }
- var preContext = preResult?.Context;
- if (preContext is not null)
- {
- contextMod = contextMod with
- {
- TunProtectSsPort = preContext.TunProtectSsPort,
- ProxyRelaySsPort = preContext.ProxyRelaySsPort,
- };
- }
- var result = await CoreConfigHandler.GenerateClientConfig(contextMod, fileName);
+ var result = await CoreConfigHandler.GenerateClientConfig(mainContext, fileName);
if (result.Success != true)
{
await UpdateFunc(true, result.Msg);
@@ -114,7 +88,7 @@ public class CoreManager
await WindowsUtils.RemoveTunDevice();
}
- await CoreStart(contextMod);
+ await CoreStart(mainContext);
await CoreStartPreService(preContext);
if (_processService != null)
{
diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
index becabf25..fedfbe9d 100644
--- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
+++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
@@ -546,8 +546,13 @@ public class MainWindowViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings);
return;
}
- var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, profileItem);
- var msgs = new List([..validatorResult.Errors, ..validatorResult.Warnings]);
+ var allResult = await CoreConfigContextBuilder.BuildAll(_config, profileItem);
+ var msgs = new List([.. allResult.MainResult.ValidatorResult.Errors, .. allResult.MainResult.ValidatorResult.Warnings]);
+ if (allResult.PreSocksResult is not null)
+ {
+ msgs.AddRange(allResult.PreSocksResult.ValidatorResult.Errors);
+ msgs.AddRange(allResult.PreSocksResult.ValidatorResult.Warnings);
+ }
if (msgs.Count > 0)
{
foreach (var msg in msgs)
@@ -555,7 +560,7 @@ public class MainWindowViewModel : MyReactiveObject
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
- if (!validatorResult.Success)
+ if (!allResult.Success)
{
return;
}
@@ -563,7 +568,7 @@ public class MainWindowViewModel : MyReactiveObject
await Task.Run(async () =>
{
- await LoadCore(context);
+ await LoadCore(allResult.ResolvedMainContext, allResult.PreSocksResult?.Context);
await SysProxyHandler.UpdateSysProxy(_config, false);
await Task.Delay(1000);
});
@@ -604,9 +609,9 @@ public class MainWindowViewModel : MyReactiveObject
RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled);
}
- private async Task LoadCore(CoreConfigContext? context)
+ private async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
{
- await CoreManager.Instance.LoadCore(context);
+ await CoreManager.Instance.LoadCore(mainContext, preContext);
}
#endregion core job