diff --git a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs
index 23a495f8..4894b334 100644
--- a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs
+++ b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs
@@ -5,6 +5,39 @@ 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);
+
+ ///
+ /// Merges all errors and warnings from the main result and the optional pre-socks result
+ /// into a single for unified notification.
+ ///
+ public NodeValidatorResult CombinedValidatorResult => new(
+ [.. MainResult.ValidatorResult.Errors, .. PreSocksResult?.ValidatorResult.Errors ?? []],
+ [.. MainResult.ValidatorResult.Warnings, .. PreSocksResult?.ValidatorResult.Warnings ?? []]);
+
+ ///
+ /// The main context with TunProtectSsPort/ProxyRelaySsPort and ProtectDomainList 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,
+ ProtectDomainList = [.. MainResult.Context.ProtectDomainList ?? [], .. PreSocksResult.Context.ProtectDomainList ?? []],
+ }
+ : MainResult.Context;
+}
+
public class CoreConfigContextBuilder
{
///
@@ -75,6 +108,79 @@ 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);
+ return preSocksResult with
+ {
+ Context = preSocksResult.Context with
+ {
+ ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.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);
+ return preResult2 with
+ {
+ Context = preResult2.Context with
+ {
+ ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preResult2.Context.ProtectDomainList ?? []],
+ 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 e5b6fec3..f3ef7bce 100644
--- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs
+++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs
@@ -1321,47 +1321,6 @@ public static class ConfigHandler
return itemSocks;
}
- public static CoreConfigContext? 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)
- {
- return nodeContext with { Node = preSocksItem, };
- }
-
- 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 preContext = nodeContext with
- {
- Node = preItem,
- TunProtectSsPort = tunProtectSsPort,
- ProxyRelaySsPort = proxyRelaySsPort,
- };
- return preContext;
- }
-
///
/// 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 cd30450f..b2df4562 100644
--- a/v2rayN/ServiceLib/Manager/CoreManager.cs
+++ b/v2rayN/ServiceLib/Manager/CoreManager.cs
@@ -57,27 +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 preContext = ConfigHandler.GetPreSocksCoreConfigContext(contextMod);
- 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);
@@ -96,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/Manager/NoticeManager.cs b/v2rayN/ServiceLib/Manager/NoticeManager.cs
index da034f02..184ef79c 100644
--- a/v2rayN/ServiceLib/Manager/NoticeManager.cs
+++ b/v2rayN/ServiceLib/Manager/NoticeManager.cs
@@ -38,4 +38,25 @@ public class NoticeManager
Enqueue(msg);
SendMessage(msg);
}
+
+ ///
+ /// Sends each error and warning in to the message panel
+ /// and enqueues a summary snack notification (capped at 10 messages).
+ /// Returns true when there were any messages so the caller can decide on early-return
+ /// based on .
+ ///
+ public bool NotifyValidatorResult(NodeValidatorResult validatorResult)
+ {
+ var msgs = new List([.. validatorResult.Errors, .. validatorResult.Warnings]);
+ if (msgs.Count == 0)
+ {
+ return false;
+ }
+ foreach (var msg in msgs)
+ {
+ SendMessage(msg);
+ }
+ Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
+ return true;
+ }
}
diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
index becabf25..0bd0c588 100644
--- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
+++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
@@ -546,24 +546,15 @@ 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]);
- if (msgs.Count > 0)
+ var allResult = await CoreConfigContextBuilder.BuildAll(_config, profileItem);
+ if (NoticeManager.Instance.NotifyValidatorResult(allResult.CombinedValidatorResult) && !allResult.Success)
{
- foreach (var msg in msgs)
- {
- NoticeManager.Instance.SendMessage(msg);
- }
- NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
- if (!validatorResult.Success)
- {
- return;
- }
+ return;
}
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 +595,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
diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs
index 91c745d6..2b0de98d 100644
--- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs
+++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs
@@ -764,18 +764,9 @@ public class ProfilesViewModel : MyReactiveObject
}
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item);
- var msgs = new List([..validatorResult.Errors, ..validatorResult.Warnings]);
- if (msgs.Count > 0)
+ if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success)
{
- foreach (var msg in msgs)
- {
- NoticeManager.Instance.SendMessage(msg);
- }
- NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
- if (!validatorResult.Success)
- {
- return;
- }
+ return;
}
if (blClipboard)
@@ -804,18 +795,9 @@ public class ProfilesViewModel : MyReactiveObject
return;
}
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item);
- var msgs = new List([..validatorResult.Errors, ..validatorResult.Warnings]);
- if (msgs.Count > 0)
+ if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success)
{
- foreach (var msg in msgs)
- {
- NoticeManager.Instance.SendMessage(msg);
- }
- NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
- if (!validatorResult.Success)
- {
- return;
- }
+ return;
}
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
if (result.Success != true)