This commit is contained in:
DHR60 2025-08-07 10:42:41 +00:00 committed by GitHub
commit 88c2246c4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 3968 additions and 599 deletions

View file

@ -466,11 +466,11 @@ public class Utils
return false;
}
public static int GetFreePort(int defaultPort = 9090)
public static int GetFreePort(int defaultPort = 0)
{
try
{
if (!Utils.PortInUse(defaultPort))
if (!(defaultPort == 0 || Utils.PortInUse(defaultPort)))
{
return defaultPort;
}
@ -813,7 +813,7 @@ public class Utils
}
}
public static string GetBinConfigPath(string filename = "")
public static string GetBinConfigPath(string filename = "", ECoreType coreType = ECoreType.v2rayN)
{
var tempPath = Path.Combine(StartupPath(), "binConfigs");
if (!Directory.Exists(tempPath))
@ -827,10 +827,27 @@ public class Utils
}
else
{
return Path.Combine(tempPath, filename);
return Path.Combine(tempPath, GetBinConfigFileName(filename, coreType));
}
}
public static string GetBinConfigFileName(string filename, ECoreType coreType = ECoreType.v2rayN)
{
var fileSuffix = coreType switch
{
ECoreType.sing_box => ".json",
ECoreType.Xray => ".json",
ECoreType.hysteria2 => ".json",
ECoreType.naiveproxy => ".json",
ECoreType.tuic => ".json",
ECoreType.juicity => ".json",
ECoreType.brook => ".cac",
ECoreType.shadowquic => ".yaml",
_ => string.Empty
};
return filename.EndsWith(fileSuffix) ? filename : $"{filename}{fileSuffix}";
}
#endregion TempPath
#region Platform

View file

@ -11,5 +11,10 @@ public enum EConfigType
Hysteria2 = 7,
TUIC = 8,
WireGuard = 9,
HTTP = 10
HTTP = 10,
Anytls = 11,
NaiveProxy = 100,
Juicity = 101,
Brook = 102,
Shadowquic = 103,
}

View file

@ -9,5 +9,6 @@ public enum EInboundProtocol
api,
api2,
mixed,
split,
speedtest = 21
}

View file

@ -12,8 +12,8 @@ public class Global
public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw=";
public const string ConfigFileName = "guiNConfig.json";
public const string CoreConfigFileName = "config.json";
public const string CorePreConfigFileName = "configPre.json";
public const string CoreConfigFileName = "config";
public const string CorePreConfigFileName = "configPre";
public const string CoreSpeedtestConfigFileName = "configTest{0}.json";
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
public const string ClashMixinConfigFileName = "Mixin.yaml";
@ -169,7 +169,12 @@ public class Global
{ EConfigType.Trojan, "trojan://" },
{ EConfigType.Hysteria2, "hysteria2://" },
{ EConfigType.TUIC, "tuic://" },
{ EConfigType.WireGuard, "wireguard://" }
{ EConfigType.WireGuard, "wireguard://" },
{ EConfigType.Anytls, "anytls://" },
{ EConfigType.NaiveProxy, "naive://" },
{ EConfigType.Juicity, "juicity://" },
{ EConfigType.Brook, "brook://" },
{ EConfigType.Shadowquic, "shadowquic://" }
};
public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
@ -182,7 +187,12 @@ public class Global
{ EConfigType.Trojan, "trojan" },
{ EConfigType.Hysteria2, "hysteria2" },
{ EConfigType.TUIC, "tuic" },
{ EConfigType.WireGuard, "wireguard" }
{ EConfigType.WireGuard, "wireguard" },
{ EConfigType.Anytls, "anytls" },
{ EConfigType.NaiveProxy, "naiveproxy" },
{ EConfigType.Juicity, "juicity" },
{ EConfigType.Brook, "brook" },
{ EConfigType.Shadowquic, "shadowquic" }
};
public static readonly List<string> VmessSecurities =
@ -276,6 +286,75 @@ public class Global
"sing_box"
];
public static readonly List<string> Hysteria2CoreTypes =
[
"sing_box",
"hysteria2"
];
public static readonly List<string> TuicCoreTypes =
[
"sing_box",
"tuic"
];
public static readonly List<string> NaiveProxyCoreTypes =
[
"naiveproxy"
];
public static readonly List<string> JuicityProxyCoreTypes =
[
"juicity"
];
public static readonly List<string> BrookCoreTypes =
[
"brook"
];
public static readonly List<string> ShadowquicCoreTypes =
[
"shadowquic"
];
public static readonly List<EConfigType> SupportSplitConfigTypes =
[
EConfigType.VMess,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.Hysteria2,
EConfigType.TUIC,
EConfigType.WireGuard,
EConfigType.SOCKS,
];
public static readonly HashSet<EConfigType> XraySupportConfigType =
[
EConfigType.VMess,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.WireGuard,
EConfigType.SOCKS,
EConfigType.HTTP,
];
public static readonly HashSet<EConfigType> SingboxSupportConfigType =
[
EConfigType.VMess,
EConfigType.VLESS,
EConfigType.Shadowsocks,
EConfigType.Trojan,
EConfigType.Hysteria2,
EConfigType.TUIC,
EConfigType.Anytls,
EConfigType.WireGuard,
EConfigType.SOCKS,
EConfigType.HTTP,
];
public static readonly List<string> DomainStrategies =
[
"AsIs",
@ -462,13 +541,20 @@ public class Global
""
];
public static readonly List<string> TuicCongestionControls =
public static readonly List<string> CongestionControls =
[
"cubic",
"new_reno",
"bbr"
];
public static readonly List<string> NaiveProxyProtocols =
[
"https",
"http",
"quic"
];
public static readonly List<string> allowSelectType =
[
"selector",

View file

@ -1,3 +1,7 @@
using DynamicData;
using ServiceLib.Enums;
using ServiceLib.Models;
namespace ServiceLib.Handler;
public sealed class AppHandler
@ -231,9 +235,89 @@ public sealed class AppHandler
return (ECoreType)profileItem.CoreType;
}
return GetCoreType(eConfigType);
}
public ECoreType GetCoreType(EConfigType eConfigType)
{
var item = _config.CoreTypeItem?.FirstOrDefault(it => it.ConfigType == eConfigType);
return item?.CoreType ?? ECoreType.Xray;
}
public ECoreType GetSplitCoreType(ProfileItem profileItem, EConfigType eConfigType)
{
if (profileItem?.CoreType != null)
{
return (ECoreType)profileItem.CoreType;
}
return GetSplitCoreType(eConfigType);
}
public ECoreType GetSplitCoreType(EConfigType eConfigType)
{
var item = _config.SplitCoreItem.SplitCoreTypes?.FirstOrDefault(it => it.ConfigType == eConfigType);
return item?.CoreType ?? ECoreType.Xray;
}
public (bool, ECoreType, ECoreType?) GetCoreAndPreType(ProfileItem profileItem)
{
var splitCore = _config.SplitCoreItem.EnableSplitCore;
var coreType = GetCoreType(profileItem, profileItem.ConfigType);
ECoreType? preCoreType = null;
var pureEndpointCore = profileItem.CoreType ?? GetSplitCoreType(profileItem, profileItem.ConfigType);
var splitRouteCore = _config.SplitCoreItem.RouteCoreType;
var enableTun = _config.TunModeItem.EnableTun;
if (profileItem.ConfigType == EConfigType.Custom)
{
splitCore = false;
coreType = profileItem.CoreType ?? ECoreType.Xray;
if (profileItem.PreSocksPort > 0)
{
preCoreType = enableTun ? ECoreType.sing_box : GetCoreType(profileItem.ConfigType);
}
else
{
preCoreType = null;
}
}
else if (!splitCore && profileItem.CoreType is not (ECoreType.Xray or ECoreType.sing_box))
{
// Force SplitCore for cores that don't support direct routing (like Hysteria2, TUIC, etc.)
splitCore = true;
preCoreType = enableTun ? ECoreType.sing_box : splitRouteCore;
}
else if (splitCore)
{
// User explicitly enabled SplitCore
preCoreType = enableTun ? ECoreType.sing_box : splitRouteCore;
coreType = pureEndpointCore;
if (preCoreType == coreType)
{
preCoreType = null;
splitCore = false;
}
}
else if (enableTun) // EnableTun is true but SplitCore is false
{
// TUN mode handling for Xray/sing_box cores
preCoreType = ECoreType.sing_box;
if (preCoreType == coreType) // CoreType is sing_box
{
preCoreType = null;
}
else // CoreType is xray, etc.
{
// Force SplitCore for non-split cores
splitCore = true;
}
}
return (splitCore, coreType, preCoreType);
}
#endregion Core Type
}

View file

@ -165,6 +165,13 @@ public class ConfigHandler
config.SystemProxyItem.SystemProxyExceptions = Utils.IsWindows() ? Global.SystemProxyExceptionsWindows : Global.SystemProxyExceptionsLinux;
}
config.SplitCoreItem ??= new()
{
EnableSplitCore = false,
SplitCoreTypes = new List<CoreTypeItem>(),
RouteCoreType = ECoreType.Xray
};
return config;
}
@ -262,6 +269,11 @@ public class ConfigHandler
EConfigType.Hysteria2 => await AddHysteria2Server(config, item),
EConfigType.TUIC => await AddTuicServer(config, item),
EConfigType.WireGuard => await AddWireguardServer(config, item),
EConfigType.Anytls => await AddAnytlsServer(config, item),
EConfigType.NaiveProxy => await AddNaiveServer(config, item),
EConfigType.Juicity => await AddJuicityServer(config, item),
EConfigType.Brook => await AddBrookServer(config, item),
EConfigType.Shadowquic => await AddShadowquicServer(config, item),
_ => -1,
};
return ret;
@ -690,7 +702,7 @@ public class ConfigHandler
public static async Task<int> AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.Hysteria2;
profileItem.CoreType = ECoreType.sing_box;
//profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
@ -723,16 +735,16 @@ public class ConfigHandler
public static async Task<int> AddTuicServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.TUIC;
profileItem.CoreType = ECoreType.sing_box;
//profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Network = string.Empty;
if (!Global.TuicCongestionControls.Contains(profileItem.HeaderType))
if (!Global.CongestionControls.Contains(profileItem.HeaderType))
{
profileItem.HeaderType = Global.TuicCongestionControls.FirstOrDefault()!;
profileItem.HeaderType = Global.CongestionControls.FirstOrDefault()!;
}
if (profileItem.StreamSecurity.IsNullOrEmpty())
@ -786,6 +798,173 @@ public class ConfigHandler
return 0;
}
/// <summary>
/// Add or edit a Anytls server
/// Validates and processes Anytls-specific settings
/// </summary>
/// <param name="config">Current configuration</param>
/// <param name="profileItem">Anytls profile to add</param>
/// <param name="toFile">Whether to save to file</param>
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> AddAnytlsServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.Anytls;
profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Network = string.Empty;
if (profileItem.StreamSecurity.IsNullOrEmpty())
{
profileItem.StreamSecurity = Global.StreamSecurity;
}
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// Add or edit a Naive server
/// Validates and processes Naive-specific settings
/// </summary>
/// <param name="config">Current configuration</param>
/// <param name="profileItem">Naive profile to add</param>
/// <param name="toFile">Whether to save to file</param>
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> AddNaiveServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.NaiveProxy;
profileItem.CoreType = ECoreType.naiveproxy;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Network = string.Empty;
if (!Global.NaiveProxyProtocols.Contains(profileItem.HeaderType))
{
profileItem.HeaderType = Global.NaiveProxyProtocols.FirstOrDefault()!;
}
if (profileItem.StreamSecurity.IsNullOrEmpty())
{
profileItem.StreamSecurity = Global.StreamSecurity;
}
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// Add or edit a Juicity server
/// Validates and processes Juicity-specific settings
/// </summary>
/// <param name="config">Current configuration</param>
/// <param name="profileItem">Juicity profile to add</param>
/// <param name="toFile">Whether to save to file</param>
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> AddJuicityServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.Juicity;
profileItem.CoreType = ECoreType.juicity;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Network = string.Empty;
if (!Global.CongestionControls.Contains(profileItem.HeaderType))
{
profileItem.HeaderType = Global.CongestionControls.FirstOrDefault()!;
}
if (profileItem.StreamSecurity.IsNullOrEmpty())
{
profileItem.StreamSecurity = Global.StreamSecurity;
}
if (profileItem.Alpn.IsNullOrEmpty())
{
profileItem.Alpn = "h3";
}
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// Add or edit a Brook server
/// Validates and processes Brook-specific settings
/// </summary>
/// <param name="config">Current configuration</param>
/// <param name="profileItem">Brook profile to add</param>
/// <param name="toFile">Whether to save to file</param>
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> AddBrookServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.Brook;
profileItem.CoreType = ECoreType.brook;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Network = string.Empty;
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// Add or edit a Shadowquic server
/// Validates and processes Shadowquic-specific settings
/// </summary>
/// <param name="config">Current configuration</param>
/// <param name="profileItem">Shadowquic profile to add</param>
/// <param name="toFile">Whether to save to file</param>
/// <returns>0 if successful, -1 if failed</returns>
public static async Task<int> AddShadowquicServer(Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.ConfigType = EConfigType.Shadowquic;
profileItem.CoreType = ECoreType.shadowquic;
profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx();
profileItem.Network = string.Empty;
if (!Global.CongestionControls.Contains(profileItem.HeaderType))
{
profileItem.HeaderType = Global.CongestionControls.FirstOrDefault()!;
}
if (profileItem.StreamSecurity.IsNullOrEmpty())
{
profileItem.StreamSecurity = Global.StreamSecurity;
}
if (profileItem.Alpn.IsNullOrEmpty())
{
profileItem.Alpn = "h3";
}
if (profileItem.Id.IsNullOrEmpty())
{
return -1;
}
await AddServerCommon(config, profileItem, toFile);
return 0;
}
/// <summary>
/// Sort the server list by the specified column
/// Updates the sort order in the profile extension data
@ -1160,43 +1339,6 @@ public class ConfigHandler
return result;
}
/// <summary>
/// Get a SOCKS server profile for pre-SOCKS functionality
/// Used when TUN mode is enabled or when a custom config has a pre-SOCKS port
/// </summary>
/// <param name="config">Current configuration</param>
/// <param name="node">Server node that might need pre-SOCKS</param>
/// <param name="coreType">Core type being used</param>
/// <returns>A SOCKS profile item or null if not needed</returns>
public static async Task<ProfileItem?> GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
{
ProfileItem? itemSocks = null;
if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
{
itemSocks = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.SOCKS,
Address = Global.Loopback,
Sni = node.Address, //Tun2SocksAddress
Port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks)
};
}
else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0))
{
var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
itemSocks = new ProfileItem()
{
CoreType = preCoreType,
ConfigType = EConfigType.SOCKS,
Address = Global.Loopback,
Port = node.PreSocksPort.Value,
};
}
await Task.CompletedTask;
return itemSocks;
}
/// <summary>
/// Remove servers with invalid test results (timeout)
/// Useful for cleaning up subscription lists
@ -1295,6 +1437,11 @@ public class ConfigHandler
EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false),
EConfigType.TUIC => await AddTuicServer(config, profileItem, false),
EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false),
EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false),
EConfigType.NaiveProxy => await AddNaiveServer(config, profileItem, false),
EConfigType.Juicity => await AddJuicityServer(config, profileItem, false),
EConfigType.Brook => await AddBrookServer(config, profileItem, false),
EConfigType.Shadowquic => await AddShadowquicServer(config, profileItem, false),
_ => -1,
};

View file

@ -35,7 +35,7 @@ public class CoreAdminHandler
{
StringBuilder sb = new();
sb.AppendLine("#!/bin/bash");
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath, coreInfo.CoreType).AppendQuotes())}";
sb.AppendLine($"sudo -S {cmdLine}");
var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);

View file

@ -1,3 +1,5 @@
using ServiceLib.Services.CoreConfig.Minimal;
namespace ServiceLib.Handler;
/// <summary>
@ -7,27 +9,26 @@ public class CoreConfigHandler
{
private static readonly string _tag = "CoreConfigHandler";
public static async Task<RetResult> GenerateClientConfig(ProfileItem node, string? fileName)
public static async Task<RetResult> GenerateClientConfig(CoreLaunchContext context, string? fileName)
{
var config = AppHandler.Instance.Config;
var result = new RetResult();
if (node.ConfigType == EConfigType.Custom)
if (context.ConfigType == EConfigType.Custom)
{
result = node.CoreType switch
{
ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName),
ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName),
_ => await GenerateClientCustomConfig(node, fileName)
};
}
else if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
{
result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node);
result = await GetCoreConfigServiceForCustom(context.CoreType).GenerateClientCustomConfig(context.Node, fileName);
}
else
{
result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node);
try
{
result = await GetCoreConfigServiceForClientConfig(context.CoreType).GenerateClientConfigContent(context.Node);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
result.Msg = ResUI.FailedGenDefaultConfiguration;
return result;
}
}
if (result.Success != true)
{
@ -41,65 +42,44 @@ public class CoreConfigHandler
return result;
}
private static async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
public static async Task<RetResult> GeneratePassthroughConfig(CoreLaunchContext context, string? fileName)
{
var ret = new RetResult();
var result = new RetResult();
try
{
if (node == null || fileName is null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (File.Exists(fileName))
{
File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail
File.Delete(fileName);
}
string addressFileName = node.Address;
if (!File.Exists(addressFileName))
{
addressFileName = Utils.GetConfigPath(addressFileName);
}
if (!File.Exists(addressFileName))
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
File.Copy(addressFileName, fileName);
File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file.
//check again
if (!File.Exists(fileName))
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
return await Task.FromResult(ret);
result = await GetCoreConfigServiceForPassthrough(context.CoreType).GeneratePassthroughConfig(context.Node);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
result.Msg = ResUI.FailedGenDefaultConfiguration;
return result;
}
if (result.Success != true)
{
return result;
}
if (fileName.IsNotEmpty() && result.Data != null)
{
await File.WriteAllTextAsync(fileName, result.Data.ToString());
}
return result;
}
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType)
{
var result = new RetResult();
if (coreType == ECoreType.sing_box)
try
{
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds);
result = await GetCoreConfigServiceForMultipleSpeedtest(coreType).GenerateClientSpeedtestConfig(selecteds);
}
else if (coreType == ECoreType.Xray)
catch (Exception ex)
{
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds);
Logging.SaveLog(_tag, ex);
result.Msg = ResUI.FailedGenDefaultConfiguration;
return result;
}
if (result.Success != true)
{
@ -109,21 +89,24 @@ public class CoreConfigHandler
return result;
}
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName)
public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, CoreLaunchContext context, ServerTestItem testItem, string fileName)
{
var result = new RetResult();
var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest);
var port = Utils.GetFreePort(initPort + testItem.QueueNum);
testItem.Port = port;
if (AppHandler.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box)
try
{
result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port);
result = await GetCoreConfigServiceForSpeedtest(context.CoreType).GenerateClientSpeedtestConfig(context.Node, port);
}
else
catch (Exception ex)
{
result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port);
Logging.SaveLog(_tag, ex);
result.Msg = ResUI.FailedGenDefaultConfiguration;
return result;
}
if (result.Success != true)
{
return result;
@ -140,7 +123,7 @@ public class CoreConfigHandler
{
result = await new CoreConfigSingboxService(config).GenerateClientMultipleLoadConfig(selecteds);
}
else
else if (coreType == ECoreType.Xray)
{
result = await new CoreConfigV2rayService(config).GenerateClientMultipleLoadConfig(selecteds, multipleLoad);
}
@ -152,4 +135,96 @@ public class CoreConfigHandler
await File.WriteAllTextAsync(fileName, result.Data.ToString());
return result;
}
private static CoreConfigServiceMinimalBase GetCoreConfigServiceForPassthrough(ECoreType coreType)
{
switch (coreType)
{
case ECoreType.sing_box:
return new CoreConfigSingboxService(AppHandler.Instance.Config);
case ECoreType.Xray:
return new CoreConfigV2rayService(AppHandler.Instance.Config);
case ECoreType.hysteria2:
return new CoreConfigHy2Service(AppHandler.Instance.Config);
case ECoreType.naiveproxy:
return new CoreConfigNaiveService(AppHandler.Instance.Config);
case ECoreType.tuic:
return new CoreConfigTuicService(AppHandler.Instance.Config);
case ECoreType.juicity:
return new CoreConfigJuicityService(AppHandler.Instance.Config);
case ECoreType.brook:
return new CoreConfigBrookService(AppHandler.Instance.Config);
case ECoreType.shadowquic:
return new CoreConfigShadowquicService(AppHandler.Instance.Config);
default:
throw new NotImplementedException($"Core type {coreType} is not implemented for passthrough configuration.");
}
}
private static CoreConfigServiceMinimalBase GetCoreConfigServiceForSpeedtest(ECoreType coreType)
{
switch (coreType)
{
case ECoreType.sing_box:
return new CoreConfigSingboxService(AppHandler.Instance.Config);
case ECoreType.Xray:
return new CoreConfigV2rayService(AppHandler.Instance.Config);
case ECoreType.hysteria2:
return new CoreConfigHy2Service(AppHandler.Instance.Config);
case ECoreType.naiveproxy:
return new CoreConfigNaiveService(AppHandler.Instance.Config);
case ECoreType.tuic:
return new CoreConfigTuicService(AppHandler.Instance.Config);
case ECoreType.juicity:
return new CoreConfigJuicityService(AppHandler.Instance.Config);
case ECoreType.brook:
return new CoreConfigBrookService(AppHandler.Instance.Config);
case ECoreType.shadowquic:
return new CoreConfigShadowquicService(AppHandler.Instance.Config);
default:
throw new NotImplementedException($"Core type {coreType} is not implemented for passthrough configuration.");
}
}
private static CoreConfigServiceBase GetCoreConfigServiceForMultipleSpeedtest(ECoreType coreType)
{
switch (coreType)
{
case ECoreType.sing_box:
return new CoreConfigSingboxService(AppHandler.Instance.Config);
case ECoreType.Xray:
return new CoreConfigV2rayService(AppHandler.Instance.Config);
default:
throw new NotImplementedException($"Core type {coreType} is not implemented for passthrough configuration.");
}
}
private static CoreConfigServiceMinimalBase GetCoreConfigServiceForCustom(ECoreType coreType)
{
switch (coreType)
{
case ECoreType.mihomo:
return new CoreConfigClashService(AppHandler.Instance.Config);
case ECoreType.sing_box:
return new CoreConfigSingboxService(AppHandler.Instance.Config);
case ECoreType.hysteria2:
return new CoreConfigHy2Service(AppHandler.Instance.Config);
default:
// CoreConfigServiceMinimalBase
return new CoreConfigV2rayService(AppHandler.Instance.Config);
}
}
private static CoreConfigServiceBase GetCoreConfigServiceForClientConfig(ECoreType coreType)
{
switch (coreType)
{
case ECoreType.sing_box:
return new CoreConfigSingboxService(AppHandler.Instance.Config);
case ECoreType.Xray:
return new CoreConfigV2rayService(AppHandler.Instance.Config);
default:
throw new NotImplementedException($"Core type {coreType} is not implemented for client configuration.");
}
}
}

View file

@ -1,5 +1,8 @@
using System.Diagnostics;
using System.Text;
using ServiceLib.Enums;
using ServiceLib.Models;
using static SQLite.SQLite3;
namespace ServiceLib.Handler;
@ -25,8 +28,6 @@ public class CoreHandler
Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable(Global.XrayLocalCert, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
// TODO Temporary addition to support proper use of sing-box v1.12
Environment.SetEnvironmentVariable("ENABLE_DEPRECATED_SPECIAL_OUTBOUNDS", "true", EnvironmentVariableTarget.Process);
//Copy the bin folder to the storage location (for init)
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
@ -73,28 +74,23 @@ public class CoreHandler
return;
}
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName);
if (result.Success != true)
// Create launch context and configure parameters
var context = new CoreLaunchContext(node, _config);
context.AdjustForConfigType();
// Start main core
if (!await CoreStart(context))
{
UpdateFunc(true, result.Msg);
return;
}
UpdateFunc(false, $"{node.GetSummary()}");
UpdateFunc(false, $"{Utils.GetRuntimeInfo()}");
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
await CoreStop();
await Task.Delay(100);
if (Utils.IsWindows() && _config.TunModeItem.EnableTun)
// Start pre-core if needed
if (!await CoreStartPreService(context))
{
await Task.Delay(100);
await WindowsUtils.RemoveTunDevice();
await CoreStop(); // Clean up main core if pre-core fails
return;
}
await CoreStart(node);
await CoreStartPreService(node);
if (_process != null)
{
UpdateFunc(true, $"{node.GetSummary()}");
@ -103,9 +99,9 @@ public class CoreHandler
public async Task<int> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
{
var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC) ? ECoreType.sing_box : ECoreType.Xray;
var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) ? ECoreType.sing_box : ECoreType.Xray;
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
var configPath = Utils.GetBinConfigPath(fileName);
var configPath = Utils.GetBinConfigPath(fileName, coreType);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);
UpdateFunc(false, result.Msg);
if (result.Success != true)
@ -134,15 +130,17 @@ public class CoreHandler
return -1;
}
var context = new CoreLaunchContext(node, _config);
context.AdjustForConfigType();
var coreType = context.CoreType;
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
var configPath = Utils.GetBinConfigPath(fileName);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath);
var configPath = Utils.GetBinConfigPath(fileName, coreType);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, context, testItem, configPath);
if (result.Success != true)
{
return -1;
}
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
var proc = await RunProcess(coreInfo, fileName, true, false);
if (proc is null)
@ -183,43 +181,84 @@ public class CoreHandler
#region Private
private async Task CoreStart(ProfileItem node)
private async Task<bool> CoreStart(CoreLaunchContext context)
{
var coreType = _config.RunningCoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
var coreType = context.SplitCore ? context.PureEndpointCore : context.CoreType;
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName, coreType);
var result = context.SplitCore
? await CoreConfigHandler.GeneratePassthroughConfig(context, fileName)
: await CoreConfigHandler.GenerateClientConfig(context, fileName);
if (result.Success != true)
{
UpdateFunc(true, result.Msg);
return false;
}
UpdateFunc(false, $"{context.Node.GetSummary()}");
UpdateFunc(false, $"{Utils.GetRuntimeInfo()}");
UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
await CoreStop();
await Task.Delay(100);
if (Utils.IsWindows() && _config.TunModeItem.EnableTun)
{
await Task.Delay(100);
await WindowsUtils.RemoveTunDevice();
}
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(context.CoreType);
var displayLog = context.Node.ConfigType != EConfigType.Custom || context.Node.DisplayLog;
var proc = await RunProcess(coreInfo, Utils.GetBinConfigFileName(Global.CoreConfigFileName, coreType), displayLog, true);
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true);
if (proc is null)
{
return;
UpdateFunc(true, ResUI.FailedToRunCore);
return false;
}
_process = proc;
_config.RunningCoreType = (ECoreType)(context.PreCoreType != null ? context.PreCoreType : coreType);
return true;
}
private async Task CoreStartPreService(ProfileItem node)
private async Task<bool> CoreStartPreService(CoreLaunchContext context)
{
if (_process != null && !_process.HasExited)
if (context.PreCoreType == null)
{
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
if (itemSocks != null)
{
var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box;
var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName);
var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName);
if (result.Success)
{
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(preCoreType);
var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true);
if (proc is null)
{
return;
}
_processPre = proc;
}
}
return true; // No pre-core needed, consider successful
}
var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName, (ECoreType)context.PreCoreType);
var itemSocks = new ProfileItem()
{
CoreType = context.PreCoreType,
ConfigType = EConfigType.SOCKS,
Address = Global.Loopback,
Sni = context.EnableTun && Utils.IsDomain(context.Node.Address) ? context.Node.Address : string.Empty, //Tun2SocksAddress
Port = context.PreSocksPort
};
var itemSocksLaunch = new CoreLaunchContext(itemSocks, _config);
var result = await CoreConfigHandler.GenerateClientConfig(itemSocksLaunch, fileName);
if (!result.Success)
{
UpdateFunc(true, result.Msg);
return false;
}
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo((ECoreType)context.PreCoreType);
var proc = await RunProcess(coreInfo, Utils.GetBinConfigFileName(Global.CorePreConfigFileName, (ECoreType)context.PreCoreType), true, true);
if (proc is null || (_process?.HasExited == true))
{
UpdateFunc(true, ResUI.FailedToRunCore);
return false;
}
_processPre = proc;
return true;
}
private void UpdateFunc(bool notify, string msg)
@ -269,7 +308,7 @@ public class CoreHandler
StartInfo = new()
{
FileName = fileName,
Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath),
Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath, coreInfo.CoreType).AppendQuotes() : configPath),
WorkingDirectory = Utils.GetBinConfigPath(),
UseShellExecute = false,
RedirectStandardOutput = displayLog,

View file

@ -130,7 +130,7 @@ public sealed class CoreInfoHandler
{
CoreType = ECoreType.hysteria,
CoreExes = ["hysteria"],
Arguments = "",
Arguments = "-c {0}",
Url = GetCoreUrl(ECoreType.hysteria),
},
@ -180,7 +180,7 @@ public sealed class CoreInfoHandler
{
CoreType = ECoreType.hysteria2,
CoreExes = ["hysteria-windows-amd64", "hysteria-linux-amd64", "hysteria"],
Arguments = "",
Arguments = "-c {0}",
Url = GetCoreUrl(ECoreType.hysteria2),
},

View file

@ -0,0 +1,49 @@
using static QRCoder.PayloadGenerator;
namespace ServiceLib.Handler.Fmt;
public class AnytlsFmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
var parsedUrl = Utils.TryUri(str);
if (parsedUrl == null)
{
return null;
}
ProfileItem item = new()
{
ConfigType = EConfigType.Anytls,
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
Address = parsedUrl.IdnHost,
Port = parsedUrl.Port,
};
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
item.Id = rawUserInfo;
var query = Utils.ParseQueryString(parsedUrl.Query);
_ = ResolveStdTransport(query, ref item);
return item;
}
public static string? ToUri(ProfileItem? item)
{
if (item == null)
{
return null;
}
var remark = string.Empty;
if (item.Remarks.IsNotEmpty())
{
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var pw = item.Id;
var dicQuery = new Dictionary<string, string>();
_ = GetStdTransport(item, Global.None, ref dicQuery);
return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark);
}
}

View file

@ -0,0 +1,49 @@
using static QRCoder.PayloadGenerator;
namespace ServiceLib.Handler.Fmt;
public class BrookFmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
var parsedUrl = Utils.TryUri(str);
if (parsedUrl == null)
{
return null;
}
ProfileItem item = new()
{
ConfigType = EConfigType.Brook,
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
Address = parsedUrl.IdnHost,
Port = parsedUrl.Port,
};
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
item.Id = rawUserInfo;
var query = Utils.ParseQueryString(parsedUrl.Query);
_ = ResolveStdTransport(query, ref item);
return item;
}
public static string? ToUri(ProfileItem? item)
{
if (item == null)
{
return null;
}
var remark = string.Empty;
if (item.Remarks.IsNotEmpty())
{
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var pw = item.Id;
var dicQuery = new Dictionary<string, string>();
_ = GetStdTransport(item, Global.None, ref dicQuery);
return ToUri(EConfigType.Brook, item.Address, item.Port, pw, dicQuery, remark);
}
}

View file

@ -18,6 +18,11 @@ public class FmtHandler
EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item),
EConfigType.TUIC => TuicFmt.ToUri(item),
EConfigType.WireGuard => WireguardFmt.ToUri(item),
EConfigType.Anytls => AnytlsFmt.ToUri(item),
EConfigType.NaiveProxy => NaiveFmt.ToUri(item),
EConfigType.Juicity => JuicityFmt.ToUri(item),
EConfigType.Brook => BrookFmt.ToUri(item),
EConfigType.Shadowquic => ShadowquicFmt.ToUri(item),
_ => null,
};
@ -75,6 +80,26 @@ public class FmtHandler
{
return WireguardFmt.Resolve(str, out msg);
}
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Anytls]))
{
return AnytlsFmt.Resolve(str, out msg);
}
else if (str.StartsWith(Global.ProtocolShares[EConfigType.NaiveProxy]))
{
return NaiveFmt.Resolve(str, out msg);
}
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Juicity]))
{
return JuicityFmt.Resolve(str, out msg);
}
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Brook]))
{
return BrookFmt.Resolve(str, out msg);
}
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Shadowquic]))
{
return ShadowquicFmt.Resolve(str, out msg);
}
else
{
msg = ResUI.NonvmessOrssProtocol;

View file

@ -0,0 +1,63 @@
namespace ServiceLib.Handler.Fmt;
public class JuicityFmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
ProfileItem item = new()
{
ConfigType = EConfigType.Juicity
};
var url = Utils.TryUri(str);
if (url == null)
{
return null;
}
item.Address = url.IdnHost;
item.Port = url.Port;
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
var rawUserInfo = Utils.UrlDecode(url.UserInfo);
var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
if (userInfoParts.Length == 2)
{
item.Id = userInfoParts.First();
item.Security = userInfoParts.Last();
}
var query = Utils.ParseQueryString(url.Query);
ResolveStdTransport(query, ref item);
item.HeaderType = query["congestion_control"] ?? "";
return item;
}
public static string? ToUri(ProfileItem? item)
{
if (item == null)
{
return null;
}
var remark = string.Empty;
if (item.Remarks.IsNotEmpty())
{
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var dicQuery = new Dictionary<string, string>();
if (item.Sni.IsNotEmpty())
{
dicQuery.Add("sni", item.Sni);
}
if (item.Alpn.IsNotEmpty())
{
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
}
dicQuery.Add("congestion_control", item.HeaderType);
return ToUri(EConfigType.Juicity, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark);
}
}

View file

@ -0,0 +1,52 @@
using static QRCoder.PayloadGenerator;
namespace ServiceLib.Handler.Fmt;
public class NaiveFmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
var parsedUrl = Utils.TryUri(str);
if (parsedUrl == null)
{
return null;
}
ProfileItem item = new()
{
ConfigType = EConfigType.NaiveProxy,
Remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped),
Address = parsedUrl.IdnHost,
Port = parsedUrl.Port,
};
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
item.Id = rawUserInfo;
var query = Utils.ParseQueryString(parsedUrl.Query);
_ = ResolveStdTransport(query, ref item);
item.HeaderType = query["protocol"] ?? "";
return item;
}
public static string? ToUri(ProfileItem? item)
{
if (item == null)
{
return null;
}
var remark = string.Empty;
if (item.Remarks.IsNotEmpty())
{
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var pw = item.Id;
var dicQuery = new Dictionary<string, string>();
_ = GetStdTransport(item, Global.None, ref dicQuery);
dicQuery.Add("protocol", item.HeaderType);
return ToUri(EConfigType.NaiveProxy, item.Address, item.Port, pw, dicQuery, remark);
}
}

View file

@ -0,0 +1,63 @@
namespace ServiceLib.Handler.Fmt;
public class ShadowquicFmt : BaseFmt
{
public static ProfileItem? Resolve(string str, out string msg)
{
msg = ResUI.ConfigurationFormatIncorrect;
ProfileItem item = new()
{
ConfigType = EConfigType.Shadowquic
};
var url = Utils.TryUri(str);
if (url == null)
{
return null;
}
item.Address = url.IdnHost;
item.Port = url.Port;
item.Remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
var rawUserInfo = Utils.UrlDecode(url.UserInfo);
var userInfoParts = rawUserInfo.Split(new[] { ':' }, 2);
if (userInfoParts.Length == 2)
{
item.Id = userInfoParts.First();
item.Security = userInfoParts.Last();
}
var query = Utils.ParseQueryString(url.Query);
ResolveStdTransport(query, ref item);
item.HeaderType = query["congestion_control"] ?? "";
return item;
}
public static string? ToUri(ProfileItem? item)
{
if (item == null)
{
return null;
}
var remark = string.Empty;
if (item.Remarks.IsNotEmpty())
{
remark = "#" + Utils.UrlEncode(item.Remarks);
}
var dicQuery = new Dictionary<string, string>();
if (item.Sni.IsNotEmpty())
{
dicQuery.Add("sni", item.Sni);
}
if (item.Alpn.IsNotEmpty())
{
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
}
dicQuery.Add("congestion_control", item.HeaderType);
return ToUri(EConfigType.Shadowquic, item.Address, item.Port, $"{item.Id}:{item.Security}", dicQuery, remark);
}
}

View file

@ -48,6 +48,7 @@ public class Config
public List<InItem> Inbound { get; set; }
public List<KeyEventItem> GlobalHotkeys { get; set; }
public List<CoreTypeItem> CoreTypeItem { get; set; }
public SplitCoreItem SplitCoreItem { get; set; }
#endregion other entities
}

View file

@ -138,6 +138,14 @@ public class CoreTypeItem
public ECoreType CoreType { get; set; }
}
[Serializable]
public class SplitCoreItem
{
public bool EnableSplitCore { get; set; }
public List<CoreTypeItem> SplitCoreTypes { get; set; }
public ECoreType RouteCoreType { get; set; }
}
[Serializable]
public class TunModeItem
{

View file

@ -0,0 +1,53 @@
namespace ServiceLib.Models;
/// <summary>
/// Core launch context that encapsulates all parameters required for launching
/// </summary>
public class CoreLaunchContext
{
public ProfileItem Node { get; set; }
public bool SplitCore { get; set; }
public ECoreType CoreType { get; set; }
public ECoreType? PreCoreType { get; set; }
public ECoreType PureEndpointCore { get; set; }
public ECoreType SplitRouteCore { get; set; }
public bool EnableTun { get; set; }
public int PreSocksPort { get; set; }
public EConfigType ConfigType { get; set; }
public CoreLaunchContext(ProfileItem node, Config config)
{
Node = node;
SplitCore = config.SplitCoreItem.EnableSplitCore;
CoreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
PureEndpointCore = AppHandler.Instance.GetSplitCoreType(node, node.ConfigType);
SplitRouteCore = config.SplitCoreItem.RouteCoreType;
EnableTun = config.TunModeItem.EnableTun;
PreSocksPort = 0;
PreCoreType = null;
ConfigType = node.ConfigType;
}
/// <summary>
/// Adjust context parameters based on configuration type
/// </summary>
public void AdjustForConfigType()
{
(SplitCore, CoreType, PreCoreType) = AppHandler.Instance.GetCoreAndPreType(Node);
if (Node.ConfigType == EConfigType.Custom)
{
if (Node.PreSocksPort > 0)
{
PreSocksPort = Node.PreSocksPort.Value;
}
else
{
EnableTun = false;
}
}
else if (PreCoreType != null)
{
PreSocksPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.split);
}
}
}

View file

@ -1,3 +1,5 @@
using System.Text.Json.Serialization;
namespace ServiceLib.Models;
public class SingboxConfig
@ -6,6 +8,7 @@ public class SingboxConfig
public Dns4Sbox? dns { get; set; }
public List<Inbound4Sbox> inbounds { get; set; }
public List<Outbound4Sbox> outbounds { get; set; }
public List<Endpoints4Sbox>? endpoints { get; set; }
public Route4Sbox route { get; set; }
public Experimental4Sbox? experimental { get; set; }
}
@ -29,7 +32,6 @@ public class Dns4Sbox
public bool? independent_cache { get; set; }
public bool? reverse_mapping { get; set; }
public string? client_subnet { get; set; }
public Fakeip4Sbox? fakeip { get; set; }
}
public class Route4Sbox
@ -37,6 +39,7 @@ public class Route4Sbox
public bool? auto_detect_interface { get; set; }
public List<Rule4Sbox> rules { get; set; }
public List<Ruleset4Sbox>? rule_set { get; set; }
public string? final { get; set; }
}
[Serializable]
@ -49,6 +52,7 @@ public class Rule4Sbox
public string? mode { get; set; }
public bool? ip_is_private { get; set; }
public string? client_subnet { get; set; }
public int? rewrite_ttl { get; set; }
public bool? invert { get; set; }
public string? clash_mode { get; set; }
public List<string>? inbound { get; set; }
@ -67,6 +71,27 @@ public class Rule4Sbox
public List<string>? process_name { get; set; }
public List<string>? rule_set { get; set; }
public List<Rule4Sbox>? rules { get; set; }
public string? action { get; set; }
public string? strategy { get; set; }
public List<string>? sniffer { get; set; }
public string? rcode { get; set; }
public List<object>? query_type { get; set; }
public List<string>? answer { get; set; }
public List<string>? ns { get; set; }
public List<string>? extra { get; set; }
public string? method { get; set; }
public bool? no_drop { get; set; }
public bool? source_ip_is_private { get; set; }
public bool? ip_accept_any { get; set; }
public int? source_port { get; set; }
public List<string>? source_port_range { get; set; }
public List<string>? network_type { get; set; }
public bool? network_is_expensive { get; set; }
public bool? network_is_constrained { get; set; }
public List<string>? wifi_ssid { get; set; }
public List<string>? wifi_bssid { get; set; }
public bool? rule_set_ip_cidr_match_source { get; set; }
public bool? rule_set_ip_cidr_accept_empty { get; set; }
}
[Serializable]
@ -76,7 +101,6 @@ public class Inbound4Sbox
public string tag { get; set; }
public string listen { get; set; }
public int? listen_port { get; set; }
public string? domain_strategy { get; set; }
public string interface_name { get; set; }
public List<string>? address { get; set; }
public int? mtu { get; set; }
@ -84,8 +108,6 @@ public class Inbound4Sbox
public bool? strict_route { get; set; }
public bool? endpoint_independent_nat { get; set; }
public string? stack { get; set; }
public bool? sniff { get; set; }
public bool? sniff_override_destination { get; set; }
public List<User4Sbox> users { get; set; }
}
@ -95,10 +117,8 @@ public class User4Sbox
public string password { get; set; }
}
public class Outbound4Sbox
public class Outbound4Sbox : BaseServer4Sbox
{
public string type { get; set; }
public string tag { get; set; }
public string? server { get; set; }
public int? server_port { get; set; }
public List<string>? server_ports { get; set; }
@ -113,7 +133,6 @@ public class Outbound4Sbox
public int? recv_window_conn { get; set; }
public int? recv_window { get; set; }
public bool? disable_mtu_discovery { get; set; }
public string? detour { get; set; }
public string? method { get; set; }
public string? username { get; set; }
public string? password { get; set; }
@ -121,21 +140,36 @@ public class Outbound4Sbox
public string? version { get; set; }
public string? network { get; set; }
public string? packet_encoding { get; set; }
public List<string>? local_address { get; set; }
public string? private_key { get; set; }
public string? peer_public_key { get; set; }
public List<int>? reserved { get; set; }
public int? mtu { get; set; }
public string? plugin { get; set; }
public string? plugin_opts { get; set; }
public Tls4Sbox? tls { get; set; }
public Multiplex4Sbox? multiplex { get; set; }
public Transport4Sbox? transport { get; set; }
public HyObfs4Sbox? obfs { get; set; }
public List<string>? outbounds { get; set; }
public bool? interrupt_exist_connections { get; set; }
}
public class Endpoints4Sbox : BaseServer4Sbox
{
public bool? system { get; set; }
public string? name { get; set; }
public int? mtu { get; set; }
public List<string> address { get; set; }
public string private_key { get; set; }
public int? listen_port { get; set; }
public string? udp_timeout { get; set; }
public int? workers { get; set; }
public List<Peer4Sbox> peers { get; set; }
}
public class Peer4Sbox
{
public string address { get; set; }
public int port { get; set; }
public string public_key { get; set; }
public string? pre_shared_key { get; set; }
public List<string> allowed_ips { get; set; }
public int? persistent_keepalive_interval { get; set; }
public List<int> reserved { get; set; }
}
public class Tls4Sbox
{
public bool enabled { get; set; }
@ -191,15 +225,25 @@ public class HyObfs4Sbox
public string? password { get; set; }
}
public class Server4Sbox
public class Server4Sbox : BaseServer4Sbox
{
public string? tag { get; set; }
public string? inet4_range { get; set; }
public string? inet6_range { get; set; }
public string? client_subnet { get; set; }
public string? server { get; set; }
public new string? domain_resolver { get; set; }
[JsonPropertyName("interface")] public string? Interface { get; set; }
public int? server_port { get; set; }
public string? path { get; set; }
public Headers4Sbox? headers { get; set; }
// public List<string>? path { get; set; } // hosts
public Dictionary<string, object>? predefined { get; set; }
// Deprecated
public string? address { get; set; }
public string? address_resolver { get; set; }
public string? address_strategy { get; set; }
public string? strategy { get; set; }
public string? detour { get; set; }
public string? client_subnet { get; set; }
// Deprecated End
}
public class Experimental4Sbox
@ -229,13 +273,6 @@ public class Stats4Sbox
public List<string>? users { get; set; }
}
public class Fakeip4Sbox
{
public bool enabled { get; set; }
public string inet4_range { get; set; }
public string inet6_range { get; set; }
}
public class CacheFile4Sbox
{
public bool enabled { get; set; }
@ -254,3 +291,33 @@ public class Ruleset4Sbox
public string? download_detour { get; set; }
public string? update_interval { get; set; }
}
public abstract class DialFields4Sbox
{
public string? detour { get; set; }
public string? bind_interface { get; set; }
public string? inet4_bind_address { get; set; }
public string? inet6_bind_address { get; set; }
public int? routing_mark { get; set; }
public bool? reuse_addr { get; set; }
public string? netns { get; set; }
public string? connect_timeout { get; set; }
public bool? tcp_fast_open { get; set; }
public bool? tcp_multi_path { get; set; }
public bool? udp_fragment { get; set; }
public Rule4Sbox? domain_resolver { get; set; } // or string
public string? network_strategy { get; set; }
public List<string>? network_type { get; set; }
public List<string>? fallback_network_type { get; set; }
public string? fallback_delay { get; set; }
public Tls4Sbox? tls { get; set; }
public Multiplex4Sbox? multiplex { get; set; }
public Transport4Sbox? transport { get; set; }
public HyObfs4Sbox? obfs { get; set; }
}
public abstract class BaseServer4Sbox : DialFields4Sbox
{
public string type { get; set; }
public string tag { get; set; }
}

View file

@ -654,6 +654,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Add [Anytls] Configuration 的本地化字符串。
/// </summary>
public static string menuAddAnytlsServer {
get {
return ResourceManager.GetString("menuAddAnytlsServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Add [Brook] Configuration 的本地化字符串。
/// </summary>
public static string menuAddBrookServer {
get {
return ResourceManager.GetString("menuAddBrookServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Add a custom configuration Configuration 的本地化字符串。
/// </summary>
@ -681,6 +699,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Add [Juicity] Configuration 的本地化字符串。
/// </summary>
public static string menuAddJuicityServer {
get {
return ResourceManager.GetString("menuAddJuicityServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Add [Naive] Configuration 的本地化字符串。
/// </summary>
public static string menuAddNaiveServer {
get {
return ResourceManager.GetString("menuAddNaiveServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。
/// </summary>
@ -708,6 +744,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Add [Shadowquic] Configuration 的本地化字符串。
/// </summary>
public static string menuAddShadowquicServer {
get {
return ResourceManager.GetString("menuAddShadowquicServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Add [Shadowsocks] Configuration 的本地化字符串。
/// </summary>
@ -2463,6 +2508,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Proxy Protocol 的本地化字符串。
/// </summary>
public static string TbHeaderType100 {
get {
return ResourceManager.GetString("TbHeaderType100", resourceCulture);
}
}
/// <summary>
/// 查找类似 Congestion control 的本地化字符串。
/// </summary>
@ -3417,6 +3471,33 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated. 的本地化字符串。
/// </summary>
public static string TbSettingsSplitCoreDoc1 {
get {
return ResourceManager.GetString("TbSettingsSplitCoreDoc1", resourceCulture);
}
}
/// <summary>
/// 查找类似 Routing Core defaults to sing-box when Tun is enabled. 的本地化字符串。
/// </summary>
public static string TbSettingsSplitCoreDoc2 {
get {
return ResourceManager.GetString("TbSettingsSplitCoreDoc2", resourceCulture);
}
}
/// <summary>
/// 查找类似 Enable separation of outbound and routing cores 的本地化字符串。
/// </summary>
public static string TbSettingsSplitCoreEnable {
get {
return ResourceManager.GetString("TbSettingsSplitCoreEnable", resourceCulture);
}
}
/// <summary>
/// 查找类似 sing-box ruleset files source (optional) 的本地化字符串。
/// </summary>

View file

@ -1401,4 +1401,31 @@
<data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>Add [Anytls] Configuration</value>
</data>
<data name="TbSettingsSplitCoreEnable" xml:space="preserve">
<value>Enable separation of outbound and routing cores</value>
</data>
<data name="TbSettingsSplitCoreDoc1" xml:space="preserve">
<value>Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated.</value>
</data>
<data name="TbSettingsSplitCoreDoc2" xml:space="preserve">
<value>Routing Core defaults to sing-box when Tun is enabled.</value>
</data>
<data name="menuAddBrookServer" xml:space="preserve">
<value>افزودن سرور [Brook]</value>
</data>
<data name="menuAddJuicityServer" xml:space="preserve">
<value>افزودن سرور [Juicity]</value>
</data>
<data name="menuAddNaiveServer" xml:space="preserve">
<value>افزودن سرور [Naive]</value>
</data>
<data name="menuAddShadowquicServer" xml:space="preserve">
<value>افزودن سرور [Shadowquic]</value>
</data>
<data name="TbHeaderType100" xml:space="preserve">
<value>Proxy Protocol</value>
</data>
</root>

View file

@ -1401,4 +1401,31 @@
<data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>[Anytls] konfiguráció hozzáadása</value>
</data>
<data name="TbSettingsSplitCoreEnable" xml:space="preserve">
<value>Enable separation of outbound and routing cores</value>
</data>
<data name="TbSettingsSplitCoreDoc1" xml:space="preserve">
<value>Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated.</value>
</data>
<data name="TbSettingsSplitCoreDoc2" xml:space="preserve">
<value>Routing Core defaults to sing-box when Tun is enabled.</value>
</data>
<data name="menuAddBrookServer" xml:space="preserve">
<value>[Brook] szerver hozzáadása</value>
</data>
<data name="menuAddJuicityServer" xml:space="preserve">
<value>[Juicity] szerver hozzáadása</value>
</data>
<data name="menuAddNaiveServer" xml:space="preserve">
<value>[Naive] szerver hozzáadása</value>
</data>
<data name="menuAddShadowquicServer" xml:space="preserve">
<value>[Shadowquic] szerver hozzáadása</value>
</data>
<data name="TbHeaderType100" xml:space="preserve">
<value>Proxy Protocol</value>
</data>
</root>

View file

@ -1401,4 +1401,31 @@
<data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>Add [Anytls] Configuration</value>
</data>
<data name="TbSettingsSplitCoreEnable" xml:space="preserve">
<value>Enable separation of outbound and routing cores</value>
</data>
<data name="TbSettingsSplitCoreDoc1" xml:space="preserve">
<value>Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated.</value>
</data>
<data name="TbSettingsSplitCoreDoc2" xml:space="preserve">
<value>Routing Core defaults to sing-box when Tun is enabled.</value>
</data>
<data name="menuAddBrookServer" xml:space="preserve">
<value>Add [Brook] Configuration</value>
</data>
<data name="menuAddJuicityServer" xml:space="preserve">
<value>Add [Juicity] Configuration</value>
</data>
<data name="menuAddNaiveServer" xml:space="preserve">
<value>Add [Naive] Configuration</value>
</data>
<data name="menuAddShadowquicServer" xml:space="preserve">
<value>Add [Shadowquic] Configuration</value>
</data>
<data name="TbHeaderType100" xml:space="preserve">
<value>Proxy Protocol</value>
</data>
</root>

View file

@ -1401,4 +1401,31 @@
<data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>Добавить сервер [Anytls]</value>
</data>
<data name="TbSettingsSplitCoreEnable" xml:space="preserve">
<value>Enable separation of outbound and routing cores</value>
</data>
<data name="TbSettingsSplitCoreDoc1" xml:space="preserve">
<value>Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated.</value>
</data>
<data name="TbSettingsSplitCoreDoc2" xml:space="preserve">
<value>Routing Core defaults to sing-box when Tun is enabled.</value>
</data>
<data name="menuAddBrookServer" xml:space="preserve">
<value>Добавить сервер [Brook]</value>
</data>
<data name="menuAddJuicityServer" xml:space="preserve">
<value>Добавить сервер [Juicity]</value>
</data>
<data name="menuAddNaiveServer" xml:space="preserve">
<value>Добавить сервер [Naive]</value>
</data>
<data name="menuAddShadowquicServer" xml:space="preserve">
<value>Добавить сервер [Shadowquic]</value>
</data>
<data name="TbHeaderType100" xml:space="preserve">
<value>Proxy Protocol</value>
</data>
</root>

View file

@ -1398,4 +1398,31 @@
<data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>添加 [Anytls] 配置文件</value>
</data>
<data name="TbSettingsSplitCoreEnable" xml:space="preserve">
<value>启用出站核心与路由核心分离</value>
</data>
<data name="TbSettingsSplitCoreDoc1" xml:space="preserve">
<value>出站与路由分离,出站与分流 Core 类型不同时,将启用两个核心</value>
</data>
<data name="TbSettingsSplitCoreDoc2" xml:space="preserve">
<value>启用 Tun 时,路由 Core 为 sing-box</value>
</data>
<data name="menuAddBrookServer" xml:space="preserve">
<value>添加 [Brook] 配置文件</value>
</data>
<data name="menuAddJuicityServer" xml:space="preserve">
<value>添加 [Juicity] 配置文件</value>
</data>
<data name="menuAddNaiveServer" xml:space="preserve">
<value>添加 [Naive] 配置文件</value>
</data>
<data name="menuAddShadowquicServer" xml:space="preserve">
<value>添加 [Shadowquic] 配置文件</value>
</data>
<data name="TbHeaderType100" xml:space="preserve">
<value>代理协议</value>
</data>
</root>

View file

@ -1398,4 +1398,31 @@
<data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value>
</data>
<data name="menuAddAnytlsServer" xml:space="preserve">
<value>新增 [Anytls] 設定檔</value>
</data>
<data name="TbSettingsSplitCoreEnable" xml:space="preserve">
<value>Enable separation of outbound and routing cores</value>
</data>
<data name="TbSettingsSplitCoreDoc1" xml:space="preserve">
<value>Outbound and routing are decoupled. If their Core types differ, two separate cores will be activated.</value>
</data>
<data name="TbSettingsSplitCoreDoc2" xml:space="preserve">
<value>Routing Core defaults to sing-box when Tun is enabled.</value>
</data>
<data name="menuAddBrookServer" xml:space="preserve">
<value>新增 [Brook] 設定檔</value>
</data>
<data name="menuAddJuicityServer" xml:space="preserve">
<value>新增 [Juicity] 設定檔</value>
</data>
<data name="menuAddNaiveServer" xml:space="preserve">
<value>新增 [Naive] 設定檔</value>
</data>
<data name="menuAddShadowquicServer" xml:space="preserve">
<value>新增 [Shadowquic] 設定檔</value>
</data>
<data name="TbHeaderType100" xml:space="preserve">
<value>Proxy Protocol</value>
</data>
</root>

View file

@ -1,4 +1,4 @@
{
{
"log": {
"level": "debug",
"timestamp": true
@ -14,22 +14,10 @@
{
"type": "direct",
"tag": "direct"
},
{
"type": "block",
"tag": "block"
},
{
"tag": "dns_out",
"type": "dns"
}
],
"route": {
"rules": [
{
"protocol": [ "dns" ],
"outbound": "dns_out"
}
]
}
}

View file

@ -2,28 +2,33 @@
"servers": [
{
"tag": "remote",
"address": "tcp://8.8.8.8",
"strategy": "prefer_ipv4",
"type": "tcp",
"server": "8.8.8.8",
"detour": "proxy"
},
{
"tag": "local",
"address": "223.5.5.5",
"strategy": "prefer_ipv4",
"detour": "direct"
},
{
"tag": "block",
"address": "rcode://success"
"type": "udp",
"server": "223.5.5.5"
}
],
"rules": [
{
"domain_suffix": [
"googleapis.cn",
"gstatic.com"
],
"server": "remote",
"strategy": "prefer_ipv4"
},
{
"rule_set": [
"geosite-cn"
],
"server": "local"
"server": "local",
"strategy": "prefer_ipv4"
}
],
"final": "remote"
"final": "remote",
"strategy": "prefer_ipv4"
}

View file

@ -2,29 +2,33 @@
"servers": [
{
"tag": "remote",
"address": "tcp://8.8.8.8",
"strategy": "prefer_ipv4",
"type": "tcp",
"server": "8.8.8.8",
"detour": "proxy"
},
{
"tag": "local",
"address": "223.5.5.5",
"strategy": "prefer_ipv4",
"detour": "direct"
},
{
"tag": "block",
"address": "rcode://success"
"type": "udp",
"server": "223.5.5.5"
}
],
"rules": [
{
"rule_set": [
"geosite-cn",
"geosite-geolocation-cn"
"domain_suffix": [
"googleapis.cn",
"gstatic.com"
],
"server": "local"
"server": "remote",
"strategy": "prefer_ipv4"
},
{
"rule_set": [
"geosite-cn"
],
"server": "local",
"strategy": "prefer_ipv4"
}
],
"final": "remote"
"final": "remote",
"strategy": "prefer_ipv4"
}

View file

@ -8,13 +8,13 @@
139,
5353
],
"outbound": "block"
"action": "reject"
},
{
"ip_cidr": [
"224.0.0.0/3",
"ff00::/8"
],
"outbound": "block"
"action": "reject"
}
]

View file

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YamlDotNet.Core.Tokens;
namespace ServiceLib.Services.CoreConfig;
public abstract class CoreConfigServiceMinimalBase(Config config)
{
public virtual string _tag => GetType().Name;
protected Config _config = config;
public virtual Task<RetResult> GeneratePassthroughConfig(ProfileItem node)
{
return GeneratePassthroughConfig(node, AppHandler.Instance.GetLocalPort(EInboundProtocol.split));
}
public virtual Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
{
return GeneratePassthroughConfig(node, port);
}
protected abstract Task<RetResult> GeneratePassthroughConfig(ProfileItem node, int port);
public virtual async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
{
var ret = new RetResult();
try
{
if (node == null || fileName is null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (File.Exists(fileName))
{
File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail
File.Delete(fileName);
}
string addressFileName = node.Address;
if (!File.Exists(addressFileName))
{
addressFileName = Utils.GetConfigPath(addressFileName);
}
if (!File.Exists(addressFileName))
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
File.Copy(addressFileName, fileName);
File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file.
//check again
if (!File.Exists(fileName))
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
return await Task.FromResult(ret);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
}
public abstract class CoreConfigServiceBase(Config config) : CoreConfigServiceMinimalBase(config)
{
public abstract Task<RetResult> GenerateClientConfigContent(ProfileItem node);
public abstract Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad);
public virtual Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds)
{
return GenerateClientMultipleLoadConfig(selecteds, EMultipleLoad.LeastPing);
}
public abstract Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds);
}

View file

@ -1,22 +1,19 @@
using System.Data;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Reactive;
using System.Text.Json.Nodes;
using DynamicData;
using ServiceLib.Models;
namespace ServiceLib.Services.CoreConfig;
public class CoreConfigSingboxService
public class CoreConfigSingboxService(Config config) : CoreConfigServiceBase(config)
{
private Config _config;
private static readonly string _tag = "CoreConfigSingboxService";
public CoreConfigSingboxService(Config config)
{
_config = config;
}
#region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
public override async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
{
var ret = new RetResult();
try
@ -53,7 +50,18 @@ public class CoreConfigSingboxService
await GenInbounds(singboxConfig);
await GenOutbound(node, singboxConfig.outbounds.First());
if (node.ConfigType == EConfigType.WireGuard)
{
singboxConfig.outbounds.RemoveAt(0);
var endpoints = new Endpoints4Sbox();
await GenEndpoint(node, endpoints);
endpoints.tag = Global.ProxyTag;
singboxConfig.endpoints = new() { endpoints };
}
else
{
await GenOutbound(node, singboxConfig.outbounds.First());
}
await GenMoreOutbounds(node, singboxConfig);
@ -78,7 +86,7 @@ public class CoreConfigSingboxService
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
public override async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
{
var ret = new RetResult();
try
@ -127,7 +135,7 @@ public class CoreConfigSingboxService
foreach (var it in selecteds)
{
if (it.ConfigType == EConfigType.Custom)
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
{
continue;
}
@ -202,16 +210,29 @@ public class CoreConfigSingboxService
continue;
}
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(item, outbound);
outbound.tag = Global.ProxyTag + inbound.listen_port.ToString();
singboxConfig.outbounds.Add(outbound);
var server = await GenServer(item);
if (server is null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
var tag = Global.ProxyTag + inbound.listen_port.ToString();
server.tag = tag;
if (server is Endpoints4Sbox endpoint)
{
singboxConfig.endpoints ??= new();
singboxConfig.endpoints.Add(endpoint);
}
else if (server is Outbound4Sbox outbound)
{
singboxConfig.outbounds.Add(outbound);
}
//rule
Rule4Sbox rule = new()
{
inbound = new List<string> { inbound.tag },
outbound = outbound.tag
outbound = tag
};
singboxConfig.route.rules.Add(rule);
}
@ -242,7 +263,7 @@ public class CoreConfigSingboxService
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
public override async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
@ -275,7 +296,18 @@ public class CoreConfigSingboxService
}
await GenLog(singboxConfig);
await GenOutbound(node, singboxConfig.outbounds.First());
if (node.ConfigType == EConfigType.WireGuard)
{
singboxConfig.outbounds.RemoveAt(0);
var endpoints = new Endpoints4Sbox();
await GenEndpoint(node, endpoints);
endpoints.tag = Global.ProxyTag;
singboxConfig.endpoints = new() { endpoints };
}
else
{
await GenOutbound(node, singboxConfig.outbounds.First());
}
await GenMoreOutbounds(node, singboxConfig);
await GenDnsDomains(null, singboxConfig, null);
@ -302,7 +334,7 @@ public class CoreConfigSingboxService
}
}
public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds)
public override async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad)
{
var ret = new RetResult();
try
@ -339,7 +371,7 @@ public class CoreConfigSingboxService
var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds)
{
if (it.ConfigType == EConfigType.Custom)
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
{
continue;
}
@ -394,7 +426,7 @@ public class CoreConfigSingboxService
}
}
public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
public override async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
{
var ret = new RetResult();
if (node == null || fileName is null)
@ -475,6 +507,92 @@ public class CoreConfigSingboxService
}
}
protected override async Task<RetResult> GeneratePassthroughConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
{
if (node == null
|| node.Port <= 0)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
if (result.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
if (singboxConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(singboxConfig);
var inbound = new Inbound4Sbox()
{
type = EInboundProtocol.mixed.ToString(),
tag = EInboundProtocol.socks.ToString(),
listen = Global.Loopback,
listen_port = port
};
singboxConfig.inbounds = new() { inbound };
if (node.ConfigType == EConfigType.WireGuard)
{
singboxConfig.outbounds.RemoveAt(0);
var endpoints = new Endpoints4Sbox();
await GenEndpoint(node, endpoints);
endpoints.tag = Global.ProxyTag;
singboxConfig.endpoints = new() { endpoints };
}
else
{
await GenOutbound(node, singboxConfig.outbounds.First());
}
if (singboxConfig.endpoints == null)
{
singboxConfig.outbounds = new() { JsonUtils.DeepCopy(singboxConfig.outbounds.First()) };
}
else
{
singboxConfig.outbounds.Clear();
}
await GenMoreOutbounds(node, singboxConfig);
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
var config = JsonNode.Parse(JsonUtils.Serialize(singboxConfig)).AsObject();
config.Remove("route");
ret.Data = JsonUtils.Serialize(config, true);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
#endregion public gen function
#region private gen function
@ -534,15 +652,6 @@ public class CoreConfigSingboxService
singboxConfig.inbounds.Add(inbound);
inbound.listen_port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks);
inbound.sniff = _config.Inbound.First().SniffingEnabled;
inbound.sniff_override_destination = _config.Inbound.First().RouteOnly ? false : _config.Inbound.First().SniffingEnabled;
inbound.domain_strategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox;
var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing.DomainStrategy4Singbox.IsNotEmpty())
{
inbound.domain_strategy = routing.DomainStrategy4Singbox;
}
if (_config.Inbound.First().SecondLocalPortEnabled)
{
@ -587,8 +696,6 @@ public class CoreConfigSingboxService
tunInbound.mtu = _config.TunModeItem.Mtu;
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
tunInbound.stack = _config.TunModeItem.Stack;
tunInbound.sniff = _config.Inbound.First().SniffingEnabled;
//tunInbound.sniff_override_destination = _config.inbound.First().routeOnly ? false : _config.inbound.First().sniffingEnabled;
if (_config.TunModeItem.EnableIPv6Address == false)
{
tunInbound.address = ["172.18.0.1/30"];
@ -621,6 +728,17 @@ public class CoreConfigSingboxService
outbound.server_port = node.Port;
outbound.type = Global.ProtocolTypes[node.ConfigType];
if (Utils.IsDomain(node.Address))
{
var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box);
var localDnsAddress = string.IsNullOrEmpty(item?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : item?.DomainDNSAddress;
outbound.domain_resolver = new()
{
server = localDnsAddress.StartsWith("tag://") ? localDnsAddress.Substring(6) : "local_resolver",
strategy = string.IsNullOrEmpty(item?.DomainStrategy4Freedom) ? null : item?.DomainStrategy4Freedom
};
}
switch (node.ConfigType)
{
case EConfigType.VMess:
@ -730,13 +848,9 @@ public class CoreConfigSingboxService
outbound.congestion_control = node.HeaderType;
break;
}
case EConfigType.WireGuard:
case EConfigType.Anytls:
{
outbound.private_key = node.Id;
outbound.peer_public_key = node.PublicKey;
outbound.reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList();
outbound.local_address = Utils.String2List(node.RequestHost);
outbound.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt();
outbound.password = node.Id;
break;
}
}
@ -752,6 +866,76 @@ public class CoreConfigSingboxService
return 0;
}
private async Task<int> GenEndpoint(ProfileItem node, Endpoints4Sbox endpoint)
{
try
{
endpoint.address = Utils.String2List(node.RequestHost);
endpoint.type = Global.ProtocolTypes[node.ConfigType];
if (Utils.IsDomain(node.Address))
{
var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box);
var localDnsAddress = string.IsNullOrEmpty(item?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : item?.DomainDNSAddress;
endpoint.domain_resolver = new()
{
server = localDnsAddress.StartsWith("tag://") ? localDnsAddress.Substring(6) : "local_resolver",
strategy = string.IsNullOrEmpty(item?.DomainStrategy4Freedom) ? null : item?.DomainStrategy4Freedom
};
}
switch (node.ConfigType)
{
case EConfigType.WireGuard:
{
var peer = new Peer4Sbox
{
public_key = node.PublicKey,
reserved = Utils.String2List(node.Path)?.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 };
break;
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<BaseServer4Sbox?> GenServer(ProfileItem node)
{
try
{
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
if (node.ConfigType == EConfigType.WireGuard)
{
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
await GenEndpoint(node, endpoint);
return endpoint;
}
else
{
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(node, outbound);
return outbound;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult<BaseServer4Sbox?>(null);
}
private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
{
try
@ -918,26 +1102,42 @@ public class CoreConfigSingboxService
}
//current proxy
var outbound = singboxConfig.outbounds.First();
BaseServer4Sbox? outbound = singboxConfig.endpoints?.FirstOrDefault(t => t.tag == Global.ProxyTag) == null ? singboxConfig.outbounds.First() : null;
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
//Previous proxy
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
string? prevOutboundTag = null;
if (prevNode is not null
&& prevNode.ConfigType != EConfigType.Custom)
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType))
{
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(prevNode, prevOutbound);
prevOutboundTag = $"prev-{Global.ProxyTag}";
prevOutbound.tag = prevOutboundTag;
singboxConfig.outbounds.Add(prevOutbound);
var prevServer = await GenServer(prevNode);
prevServer.tag = prevOutboundTag;
if (prevServer is Endpoints4Sbox endpoint)
{
singboxConfig.endpoints ??= new();
singboxConfig.endpoints.Add(endpoint);
}
else if (prevServer is Outbound4Sbox outboundPrev)
{
singboxConfig.outbounds.Add(outboundPrev);
}
}
var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
var nextServer = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
if (nextOutbound is not null)
if (nextServer is not null)
{
singboxConfig.outbounds.Insert(0, nextOutbound);
if (nextServer is Endpoints4Sbox endpoint)
{
singboxConfig.endpoints ??= new();
singboxConfig.endpoints.Insert(0, endpoint);
}
else if (nextServer is Outbound4Sbox outboundNext)
{
singboxConfig.outbounds.Insert(0, outboundNext);
}
}
}
catch (Exception ex)
@ -960,11 +1160,13 @@ public class CoreConfigSingboxService
}
var resultOutbounds = new List<Outbound4Sbox>();
var resultEndpoints = new List<Endpoints4Sbox>(); // For endpoints
var prevOutbounds = new List<Outbound4Sbox>(); // Separate list for prev outbounds
var prevEndpoints = new List<Endpoints4Sbox>(); // Separate list for prev endpoints
var proxyTags = new List<string>(); // For selector and urltest outbounds
// Cache for chain proxies to avoid duplicate generation
var nextProxyCache = new Dictionary<string, Outbound4Sbox?>();
var nextProxyCache = new Dictionary<string, BaseServer4Sbox?>();
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
int prevIndex = 0; // Index for prev outbounds
@ -976,19 +1178,18 @@ public class CoreConfigSingboxService
// Handle proxy chain
string? prevTag = null;
var currentOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null);
if (nextOutbound != null)
var currentServer = await GenServer(node);
var nextServer = nextProxyCache.GetValueOrDefault(node.Subid, null);
if (nextServer != null)
{
nextOutbound = JsonUtils.DeepCopy(nextOutbound);
nextServer = JsonUtils.DeepCopy(nextServer);
}
var subItem = await AppHandler.Instance.GetSubItem(node.Subid);
// current proxy
await GenOutbound(node, currentOutbound);
currentOutbound.tag = $"{Global.ProxyTag}-{index}";
proxyTags.Add(currentOutbound.tag);
currentServer.tag = $"{Global.ProxyTag}-{index}";
proxyTags.Add(currentServer.tag);
if (!node.Subid.IsNullOrEmpty())
{
@ -1000,7 +1201,7 @@ public class CoreConfigSingboxService
{
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
if (prevNode is not null
&& prevNode.ConfigType != EConfigType.Custom)
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType))
{
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(prevNode, prevOutbound);
@ -1011,18 +1212,32 @@ public class CoreConfigSingboxService
prevProxyTags[node.Subid] = prevTag;
}
nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound);
nextServer = await GenChainOutbounds(subItem, currentServer, prevTag, nextServer);
if (!nextProxyCache.ContainsKey(node.Subid))
{
nextProxyCache[node.Subid] = nextOutbound;
nextProxyCache[node.Subid] = nextServer;
}
}
if (nextOutbound is not null)
if (nextServer is not null)
{
resultOutbounds.Add(nextOutbound);
if (nextServer is Endpoints4Sbox nextEndpoint)
{
resultEndpoints.Add(nextEndpoint);
}
else if (nextServer is Outbound4Sbox nextOutbound)
{
resultOutbounds.Add(nextOutbound);
}
}
if (currentServer is Endpoints4Sbox currentEndpoint)
{
resultEndpoints.Add(currentEndpoint);
}
else if (currentServer is Outbound4Sbox currentOutbound)
{
resultOutbounds.Add(currentOutbound);
}
resultOutbounds.Add(currentOutbound);
}
// Add urltest outbound (auto selection based on latency)
@ -1055,6 +1270,9 @@ public class CoreConfigSingboxService
resultOutbounds.AddRange(prevOutbounds);
resultOutbounds.AddRange(singboxConfig.outbounds);
singboxConfig.outbounds = resultOutbounds;
singboxConfig.endpoints ??= new List<Endpoints4Sbox>();
resultEndpoints.AddRange(singboxConfig.endpoints);
singboxConfig.endpoints = resultEndpoints;
}
catch (Exception ex)
{
@ -1076,7 +1294,7 @@ public class CoreConfigSingboxService
/// <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)
private async Task<BaseServer4Sbox?> GenChainOutbounds(SubItem subItem, BaseServer4Sbox outbound, string? prevOutboundTag, BaseServer4Sbox? nextOutbound = null)
{
try
{
@ -1090,13 +1308,9 @@ public class CoreConfigSingboxService
// Next proxy
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (nextNode is not null
&& nextNode.ConfigType != EConfigType.Custom)
&& Global.SingboxSupportConfigType.Contains(nextNode.ConfigType))
{
if (nextOutbound == null)
{
nextOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(nextNode, nextOutbound);
}
nextOutbound ??= await GenServer(nextNode);
nextOutbound.tag = outbound.tag;
outbound.tag = $"mid-{outbound.tag}";
@ -1115,7 +1329,7 @@ public class CoreConfigSingboxService
{
try
{
var dnsOutbound = "dns_out";
singboxConfig.route.final = Global.ProxyTag;
if (_config.TunModeItem.EnableTun)
{
@ -1131,7 +1345,7 @@ public class CoreConfigSingboxService
singboxConfig.route.rules.Add(new()
{
port = new() { 53 },
outbound = dnsOutbound,
action = "hijack-dns",
process_name = lstDnsExe
});
@ -1142,13 +1356,25 @@ public class CoreConfigSingboxService
});
}
if (!_config.Inbound.First().SniffingEnabled)
if (_config.Inbound.First().SniffingEnabled)
{
singboxConfig.route.rules.Add(new()
{
port = [53],
network = ["udp"],
outbound = dnsOutbound
action = "sniff"
});
singboxConfig.route.rules.Add(new()
{
protocol = new() { "dns" },
action = "hijack-dns"
});
}
else
{
singboxConfig.route.rules.Add(new()
{
port = new() { 53 },
network = new() { "udp" },
action = "hijack-dns"
});
}
@ -1163,7 +1389,24 @@ public class CoreConfigSingboxService
clash_mode = ERuleMode.Global.ToString()
});
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox;
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config);
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty())
{
domainStrategy = defaultRouting.DomainStrategy4Singbox;
}
var resolveRule = new Rule4Sbox
{
action = "resolve",
strategy = domainStrategy
};
if (_config.RoutingBasicItem.DomainStrategy == "IPOnDemand")
{
singboxConfig.route.rules.Add(resolveRule);
}
var routing = await ConfigHandler.GetDefaultRouting(_config);
var ipRules = new List<RulesItem>();
if (routing != null)
{
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
@ -1172,9 +1415,21 @@ public class CoreConfigSingboxService
if (item.Enabled)
{
await GenRoutingUserRule(item, singboxConfig);
if (item.Ip != null && item.Ip.Count > 0)
{
ipRules.Add(item);
}
}
}
}
if (_config.RoutingBasicItem.DomainStrategy == "IPIfNonMatch")
{
singboxConfig.route.rules.Add(resolveRule);
foreach (var item in ipRules)
{
await GenRoutingUserRule(item, singboxConfig);
}
}
}
catch (Exception ex)
{
@ -1222,10 +1477,15 @@ public class CoreConfigSingboxService
item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig);
var rules = singboxConfig.route.rules;
var rule = new Rule4Sbox()
var rule = new Rule4Sbox();
if (item.OutboundTag == "block")
{
outbound = item.OutboundTag,
};
rule.action = "reject";
}
else
{
rule.outbound = item.OutboundTag;
}
if (item.Port.IsNotEmpty())
{
@ -1349,24 +1609,28 @@ public class CoreConfigSingboxService
{
return false;
}
else if (address.StartsWith("geoip:!"))
{
return false;
}
else if (address.Equals("geoip:private"))
{
rule.ip_is_private = true;
}
else if (address.StartsWith("geoip:"))
{
if (rule.geoip is null)
{ rule.geoip = new(); }
rule.geoip ??= new();
rule.geoip?.Add(address.Substring(6));
}
else if (address.Equals("geoip:!private"))
{
rule.ip_is_private = false;
}
else if (address.StartsWith("geoip:!"))
{
rule.geoip ??= new();
rule.geoip?.Add(address.Substring(6));
rule.invert = true;
}
else
{
if (rule.ip_cidr is null)
{ rule.ip_cidr = new(); }
rule.ip_cidr ??= new();
rule.ip_cidr?.Add(address);
}
return true;
@ -1381,18 +1645,29 @@ public class CoreConfigSingboxService
var node = await AppHandler.Instance.GetProfileItemViaRemarks(outboundTag);
if (node == null
|| node.ConfigType == EConfigType.Custom)
|| !Global.SingboxSupportConfigType.Contains(node.ConfigType))
{
return Global.ProxyTag;
}
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(node, outbound);
outbound.tag = Global.ProxyTag + node.IndexId.ToString();
singboxConfig.outbounds.Add(outbound);
var server = await GenServer(node);
if (server is null)
{
return Global.ProxyTag;
}
return outbound.tag;
server.tag = Global.ProxyTag + node.IndexId.ToString();
if (server is Endpoints4Sbox endpoint)
{
singboxConfig.endpoints ??= new();
singboxConfig.endpoints.Add(endpoint);
}
else if (server is Outbound4Sbox outbound)
{
singboxConfig.outbounds.Add(outbound);
}
return server.tag;
}
private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig)
@ -1417,7 +1692,14 @@ public class CoreConfigSingboxService
}
singboxConfig.dns = dns4Sbox;
await GenDnsDomains(node, singboxConfig, item);
if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty())
{
await GenDnsDomains(node, singboxConfig, item);
}
else
{
await GenDnsDomainsLegacy(node, singboxConfig, item);
}
}
catch (Exception ex)
{
@ -1432,6 +1714,75 @@ public class CoreConfigSingboxService
dns4Sbox.servers ??= [];
dns4Sbox.rules ??= [];
var tag = "local_resolver";
var localDnsAddress = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress;
if (localDnsAddress.StartsWith("tag://"))
{
tag = localDnsAddress.Substring(6);
var localDnsTag = "local_local";
dns4Sbox.servers.Add(new()
{
tag = localDnsTag,
type = "local"
});
dns4Sbox.rules.Insert(0, new()
{
server = localDnsTag,
clash_mode = ERuleMode.Direct.ToString()
});
}
else
{
var (dnsType, dnsHost, dnsPort, dnsPath) = ParseDnsAddress(localDnsAddress);
dns4Sbox.servers.Add(new()
{
tag = tag,
type = dnsType,
server = dnsHost,
Interface = dnsType == "dhcp" ? dnsHost : null,
server_port = dnsPort,
path = dnsPath
});
dns4Sbox.rules.Insert(0, new()
{
server = tag,
clash_mode = ERuleMode.Direct.ToString()
});
}
dns4Sbox.rules.Insert(0, new()
{
server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote",
clash_mode = ERuleMode.Global.ToString()
});
//Tun2SocksAddress
if (_config.TunModeItem.EnableTun && node?.ConfigType == EConfigType.SOCKS && Utils.IsDomain(node?.Sni))
{
dns4Sbox.rules.Insert(0, new()
{
server = tag,
domain = [node?.Sni],
strategy = string.IsNullOrEmpty(dNSItem?.DomainStrategy4Freedom) ? null : dNSItem?.DomainStrategy4Freedom
});
}
singboxConfig.dns = dns4Sbox;
return await Task.FromResult(0);
}
private async Task<int> GenDnsDomainsLegacy(ProfileItem? node, SingboxConfig singboxConfig, DNSItem? dNSItem)
{
var dns4Sbox = singboxConfig.dns ?? new();
dns4Sbox.servers ??= [];
dns4Sbox.rules ??= [];
var tag = "local_local";
dns4Sbox.servers.Add(new()
{
@ -1479,6 +1830,91 @@ public class CoreConfigSingboxService
return await Task.FromResult(0);
}
private (string type, string? host, int? port, string? path) ParseDnsAddress(string address)
{
string type = "udp";
string? host = null;
int? port = null;
string? path = null;
if (address is "local" or "localhost")
{
return ("local", null, null, null);
}
if (address.StartsWith("dhcp://", StringComparison.OrdinalIgnoreCase))
{
string interface_name = address.Substring(7);
return ("dhcp", interface_name == "auto" ? null : interface_name, null, null);
}
if (!address.Contains("://"))
{
// udp dns
host = address;
return (type, host, port, path);
}
try
{
int protocolEndIndex = address.IndexOf("://", StringComparison.Ordinal);
type = address.Substring(0, protocolEndIndex).ToLower();
var uri = new Uri(address);
host = uri.Host;
if (!uri.IsDefaultPort)
{
port = uri.Port;
}
if ((type == "https" || type == "h3") && !string.IsNullOrEmpty(uri.AbsolutePath) && uri.AbsolutePath != "/")
{
path = uri.AbsolutePath;
}
}
catch (UriFormatException)
{
int protocolEndIndex = address.IndexOf("://", StringComparison.Ordinal);
if (protocolEndIndex > 0)
{
type = address.Substring(0, protocolEndIndex).ToLower();
string remaining = address.Substring(protocolEndIndex + 3);
int portIndex = remaining.IndexOf(':');
int pathIndex = remaining.IndexOf('/');
if (portIndex > 0)
{
host = remaining.Substring(0, portIndex);
string portPart = pathIndex > portIndex
? remaining.Substring(portIndex + 1, pathIndex - portIndex - 1)
: remaining.Substring(portIndex + 1);
if (int.TryParse(portPart, out int parsedPort))
{
port = parsedPort;
}
}
else if (pathIndex > 0)
{
host = remaining.Substring(0, pathIndex);
}
else
{
host = remaining;
}
if (pathIndex > 0 && (type == "https" || type == "h3"))
{
path = remaining.Substring(pathIndex);
}
}
}
return (type, host, port, path);
}
private async Task<int> GenExperimental(SingboxConfig singboxConfig)
{
//if (_config.guiItem.enableStatistics)

View file

@ -1,22 +1,15 @@
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Text.Json.Nodes;
namespace ServiceLib.Services.CoreConfig;
public class CoreConfigV2rayService
public class CoreConfigV2rayService(Config config) : CoreConfigServiceBase(config)
{
private Config _config;
private static readonly string _tag = "CoreConfigV2rayService";
public CoreConfigV2rayService(Config config)
{
_config = config;
}
#region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
public override async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
{
var ret = new RetResult();
try
@ -77,7 +70,7 @@ public class CoreConfigV2rayService
}
}
public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad)
public override async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad)
{
var ret = new RetResult();
@ -116,11 +109,7 @@ public class CoreConfigV2rayService
var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds)
{
if (it.ConfigType == EConfigType.Custom)
{
continue;
}
if (it.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC)
if (!Global.XraySupportConfigType.Contains(it.ConfigType))
{
continue;
}
@ -206,7 +195,7 @@ public class CoreConfigV2rayService
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
public override async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
{
var ret = new RetResult();
try
@ -255,7 +244,7 @@ public class CoreConfigV2rayService
foreach (var it in selecteds)
{
if (it.ConfigType == EConfigType.Custom)
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
{
continue;
}
@ -358,7 +347,7 @@ public class CoreConfigV2rayService
}
}
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
public override async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
@ -416,6 +405,80 @@ public class CoreConfigV2rayService
}
}
protected override async Task<RetResult> GeneratePassthroughConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
{
if (node == null
|| node.Port <= 0)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.GetNetwork() is nameof(ETransport.quic))
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
return ret;
}
ret.Msg = ResUI.InitialConfiguration;
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
if (result.IsNullOrEmpty())
{
ret.Msg = ResUI.FailedGetDefaultConfiguration;
return ret;
}
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
if (v2rayConfig == null)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenLog(v2rayConfig);
v2rayConfig.inbounds = new() { new()
{
tag = EInboundProtocol.socks.ToString(),
listen = Global.Loopback,
port = port,
protocol = EInboundProtocol.mixed.ToString(),
settings = new Inboundsettings4Ray()
{
udp = true,
auth = "noauth"
},
} };
await GenOutbound(node, v2rayConfig.outbounds.First());
v2rayConfig.outbounds = new() { JsonUtils.DeepCopy(v2rayConfig.outbounds.First()) };
await GenMoreOutbounds(node, v2rayConfig);
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
var config = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig)).AsObject();
config.Remove("routing");
ret.Data = JsonUtils.Serialize(config, true);
return ret;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
#endregion public gen function
#region private gen function
@ -638,9 +701,7 @@ public class CoreConfigV2rayService
var node = await AppHandler.Instance.GetProfileItemViaRemarks(outboundTag);
if (node == null
|| node.ConfigType == EConfigType.Custom
|| node.ConfigType == EConfigType.Hysteria2
|| node.ConfigType == EConfigType.TUIC)
|| !Global.SingboxSupportConfigType.Contains(node.ConfigType))
{
return Global.ProxyTag;
}
@ -1219,9 +1280,7 @@ public class CoreConfigV2rayService
// Previous proxy
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
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)
&& Utils.IsDomain(prevNode.Address))
{
domainList.Add(prevNode.Address);
@ -1230,9 +1289,7 @@ public class CoreConfigV2rayService
// 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
&& Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)
&& Utils.IsDomain(nextNode.Address))
{
domainList.Add(nextNode.Address);
@ -1348,9 +1405,7 @@ public class CoreConfigV2rayService
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
string? prevOutboundTag = null;
if (prevNode is not null
&& prevNode.ConfigType != EConfigType.Custom
&& prevNode.ConfigType != EConfigType.Hysteria2
&& prevNode.ConfigType != EConfigType.TUIC)
&& Global.XraySupportConfigType.Contains(prevNode.ConfigType))
{
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(prevNode, prevOutbound);
@ -1423,9 +1478,7 @@ public class CoreConfigV2rayService
{
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)
&& !Global.XraySupportConfigType.Contains(prevNode.ConfigType))
{
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(prevNode, prevOutbound);
@ -1492,9 +1545,7 @@ public class CoreConfigV2rayService
// 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)
&& !Global.XraySupportConfigType.Contains(nextNode.ConfigType))
{
if (nextOutbound == null)
{

View file

@ -0,0 +1,43 @@
namespace ServiceLib.Services.CoreConfig.Minimal;
public class CoreConfigBrookService(Config config) : CoreConfigServiceMinimalBase(config)
{
protected override async Task<RetResult> GeneratePassthroughConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
{
if (node == null
|| node.Port <= 0)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.ConfigType != EConfigType.Brook)
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}";
return ret;
}
var processArgs = "client";
// inbound
processArgs += " --socks5 " + Global.Loopback + ":" + port.ToString();
// outbound
processArgs += " --server " + node.Address + ":" + node.Port;
processArgs += " --password " + node.Id;
ret.Success = true;
ret.Data = processArgs;
return await Task.FromResult(ret);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return await Task.FromResult(ret);
}
}
}

View file

@ -1,19 +1,21 @@
namespace ServiceLib.Services.CoreConfig;
namespace ServiceLib.Services.CoreConfig.Minimal;
/// <summary>
/// Core configuration file processing class
/// </summary>
public class CoreConfigClashService
public class CoreConfigClashService(Config config) : CoreConfigServiceMinimalBase(config)
{
private Config _config;
private static readonly string _tag = "CoreConfigClashService";
public CoreConfigClashService(Config config)
protected override async Task<RetResult> GeneratePassthroughConfig(ProfileItem node, int port)
{
_config = config;
var ret = new RetResult
{
Success = false,
Msg = ResUI.OperationFailed
};
return await Task.FromResult(ret);
}
public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
public override async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
{
var ret = new RetResult();
if (node == null || fileName is null)

View file

@ -0,0 +1,171 @@
using System.Text.Json.Nodes;
namespace ServiceLib.Services.CoreConfig.Minimal;
public class CoreConfigHy2Service(Config config) : CoreConfigServiceMinimalBase(config)
{
protected override async Task<RetResult> GeneratePassthroughConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
{
if (node == null
|| node.Port <= 0)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.ConfigType != EConfigType.Hysteria2)
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}";
return ret;
}
var configJsonNode = new JsonObject();
// inbound
configJsonNode["socks5"] = new JsonObject
{
["listen"] = Global.Loopback + ":" + port.ToString()
};
// outbound
var outboundPort = string.Empty;
if (node.Ports.IsNotEmpty())
{
outboundPort = node.Ports.Replace(':', '-');
if (_config.HysteriaItem.HopInterval > 0)
{
configJsonNode["transport"] = new JsonObject
{
["udp"] = new JsonObject
{
["hopInterval"] = $"{_config.HysteriaItem.HopInterval}s"
}
};
}
}
else
{
outboundPort = node.Port.ToString();
}
configJsonNode["server"] = node.Address + ":" + outboundPort;
configJsonNode["auth"] = node.Id;
if (node.Sni.IsNotEmpty())
{
configJsonNode["tls"] = new JsonObject
{
["sni"] = node.Sni,
["insecure"] = node.AllowInsecure.ToLower() == "true"
};
}
if (node.Path.IsNotEmpty())
{
configJsonNode["obfs"] = new JsonObject
{
["type"] = "salamander ",
["salamander"] = new JsonObject
{
["password"] = node.Path
}
};
}
var bandwidthObject = new JsonObject();
if (_config.HysteriaItem.UpMbps > 0)
{
bandwidthObject["up"] = $"{_config.HysteriaItem.UpMbps} mbps";
}
if (_config.HysteriaItem.DownMbps > 0)
{
bandwidthObject["down"] = $"{_config.HysteriaItem.DownMbps} mbps";
}
if (bandwidthObject.Count > 0)
{
configJsonNode["bandwidth"] = bandwidthObject;
}
ret.Success = true;
ret.Data = JsonUtils.Serialize(configJsonNode, true);
return await Task.FromResult(ret);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return await Task.FromResult(ret);
}
}
public override async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
{
var ret = new RetResult();
try
{
if (node == null || fileName is null)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (File.Exists(fileName))
{
File.SetAttributes(fileName, FileAttributes.Normal); //If the file has a read-only attribute, direct deletion will fail
File.Delete(fileName);
}
string addressFileName = node.Address;
if (!File.Exists(addressFileName))
{
addressFileName = Utils.GetConfigPath(addressFileName);
}
if (!File.Exists(addressFileName))
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
// Try deserializing the file to check if it is a valid JSON or YAML file
var fileContent = File.ReadAllText(addressFileName);
var jsonContent = JsonUtils.Deserialize<JsonObject>(fileContent);
if (jsonContent != null)
{
File.Copy(addressFileName, fileName);
}
else
{
// If it's YAML, convert to JSON and write it
var yamlContent = YamlUtils.FromYaml<Dictionary<string, object>>(fileContent);
if (yamlContent != null)
{
File.WriteAllText(fileName, JsonUtils.Serialize(yamlContent, true));
}
else
{
ret.Msg = ResUI.FailedReadConfiguration + "2";
return ret;
}
}
File.SetAttributes(fileName, FileAttributes.Normal); //Copy will keep the attributes of addressFileName, so we need to add write permissions to fileName just in case of addressFileName is a read-only file.
//check again
if (!File.Exists(fileName))
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
ret.Success = true;
return await Task.FromResult(ret);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
}
}

View file

@ -0,0 +1,65 @@
using System.Text.Json.Nodes;
namespace ServiceLib.Services.CoreConfig.Minimal;
public class CoreConfigJuicityService(Config config) : CoreConfigServiceMinimalBase(config)
{
protected override async Task<RetResult> GeneratePassthroughConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
{
if (node == null
|| node.Port <= 0)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.ConfigType != EConfigType.Juicity)
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}";
return ret;
}
var configJsonNode = new JsonObject();
// log
var logLevel = string.Empty;
switch (_config.CoreBasicItem.Loglevel)
{
case "warning":
logLevel = "warn";
break;
default:
logLevel = _config.CoreBasicItem.Loglevel;
break;
}
configJsonNode["log_level"] = logLevel;
// inbound
configJsonNode["listen"] = ":" + port.ToString();
// outbound
configJsonNode["server"] = node.Address + ":" + node.Port;
configJsonNode["uuid"] = node.Id;
configJsonNode["password"] = node.Security;
if (node.Sni.IsNotEmpty())
{
configJsonNode["sni"] = node.Sni;
}
configJsonNode["allow_insecure"] = node.AllowInsecure == "true";
configJsonNode["congestion_control"] = node.HeaderType;
ret.Success = true;
ret.Data = JsonUtils.Serialize(configJsonNode, true);
return await Task.FromResult(ret);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return await Task.FromResult(ret);
}
}
}

View file

@ -0,0 +1,44 @@
using System.Text.Json.Nodes;
namespace ServiceLib.Services.CoreConfig.Minimal;
public class CoreConfigNaiveService(Config config) : CoreConfigServiceMinimalBase(config)
{
protected override async Task<RetResult> GeneratePassthroughConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
{
if (node == null
|| node.Port <= 0)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.ConfigType != EConfigType.NaiveProxy)
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}";
return ret;
}
var configJsonNode = new JsonObject();
// inbound
configJsonNode["listen"] = Global.SocksProtocol + Global.Loopback + ":" + port.ToString();
// outbound
configJsonNode["proxy"] = (node.HeaderType == "quic" ? "quic://" : Global.HttpsProtocol) + node.Id + "@" + node.Address + ":" + node.Port;
ret.Success = true;
ret.Data = JsonUtils.Serialize(configJsonNode, true);
return await Task.FromResult(ret);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return await Task.FromResult(ret);
}
}
}

View file

@ -0,0 +1,86 @@
using System.Text.Json.Nodes;
namespace ServiceLib.Services.CoreConfig.Minimal;
public class CoreConfigShadowquicService(Config config) : CoreConfigServiceMinimalBase(config)
{
protected override async Task<RetResult> GeneratePassthroughConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
{
if (node == null
|| node.Port <= 0)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.ConfigType != EConfigType.Shadowquic)
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}";
return ret;
}
var configYamlNode = new Dictionary<string, object>();
// log
var logLevel = string.Empty;
switch (_config.CoreBasicItem.Loglevel)
{
case "warning":
logLevel = "warn";
break;
default:
logLevel = _config.CoreBasicItem.Loglevel;
break;
}
configYamlNode["log-level"] = logLevel;
// inbound
var inboundNode = new Dictionary<string, object>
{
["type"] = "socks5",
["listen"] = Global.Loopback + ":" + port.ToString()
};
configYamlNode["inbound"] = inboundNode;
// outbound
var alpn = new List<string>();
foreach (var item in node.GetAlpn() ?? new List<string>())
{
alpn.Add(item);
}
if (alpn.Count == 0)
{
alpn.Add("h3");
}
var outboundNode = new Dictionary<string, object>
{
["type"] = "shadowquic",
["addr"] = node.Address + ":" + node.Port,
["password"] = node.Id,
["username"] = node.Security,
["alpn"] = alpn,
["congestion-control"] = node.HeaderType,
["zero-rtt"] = true
};
if (node.Sni.IsNotEmpty())
{
outboundNode["server-name"] = node.Sni;
}
configYamlNode["outbound"] = outboundNode;
ret.Success = true;
ret.Data = YamlUtils.ToYaml(configYamlNode);
return await Task.FromResult(ret);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return await Task.FromResult(ret);
}
}
}

View file

@ -0,0 +1,78 @@
using System.Text.Json.Nodes;
namespace ServiceLib.Services.CoreConfig.Minimal;
public class CoreConfigTuicService(Config config) : CoreConfigServiceMinimalBase(config)
{
protected override async Task<RetResult> GeneratePassthroughConfig(ProfileItem node, int port)
{
var ret = new RetResult();
try
{
if (node == null
|| node.Port <= 0)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
if (node.ConfigType != EConfigType.TUIC)
{
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.ConfigType}";
return ret;
}
var configJsonNode = new JsonObject();
// log
var logLevel = string.Empty;
switch (_config.CoreBasicItem.Loglevel)
{
case "warning":
logLevel = "warn";
break;
default:
logLevel = _config.CoreBasicItem.Loglevel;
break;
}
configJsonNode["log_level"] = logLevel;
// inbound
configJsonNode["local"] = new JsonObject
{
["server"] = Global.Loopback + ":" + port.ToString()
};
// outbound
var alpn = new JsonArray();
foreach(var item in node.GetAlpn() ?? new List<string>())
{
alpn.Add(item);
}
if (alpn.Count == 0)
{
alpn.Add("h3");
}
configJsonNode["relay"] = new JsonObject
{
["server"] = node.Address + ":" + node.Port,
["uuid"] = node.Id,
["password"] = node.Security,
["udp_relay_mode"] = "quic",
["congestion_control"] = node.HeaderType,
["alpn"] = alpn
};
ret.Success = true;
ret.Data = JsonUtils.Serialize(configJsonNode, true);
return await Task.FromResult(ret);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return await Task.FromResult(ret);
}
}
}

View file

@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
@ -70,7 +71,7 @@ public class SpeedtestService
var lstSelected = new List<ServerTestItem>();
foreach (var it in selecteds)
{
if (it.ConfigType == EConfigType.Custom)
if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType)))
{
continue;
}
@ -122,7 +123,7 @@ public class SpeedtestService
List<Task> tasks = [];
foreach (var it in selecteds)
{
if (it.ConfigType == EConfigType.Custom)
if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType)))
{
continue;
}
@ -207,7 +208,7 @@ public class SpeedtestService
{
continue;
}
if (it.ConfigType == EConfigType.Custom)
if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType)))
{
continue;
}
@ -244,7 +245,7 @@ public class SpeedtestService
UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
continue;
}
if (it.ConfigType == EConfigType.Custom)
if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType)))
{
continue;
}
@ -358,8 +359,8 @@ public class SpeedtestService
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
{
List<List<ServerTestItem>> lstTest = new();
var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC)).ToList();
var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC).ToList();
var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
var lst2 = lstSelected.Where(t => Global.SingboxSupportConfigType.Contains(t.ConfigType) && !Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++)
{

View file

@ -20,10 +20,15 @@ public class MainWindowViewModel : MyReactiveObject
public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddWireguardServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddAnytlsServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaImageCmd { get; }
public ReactiveCommand<Unit, Unit> AddBrookServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddJuicityServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddNaiveServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddShadowquicServerCmd { get; }
//Subscription
public ReactiveCommand<Unit, Unit> SubSettingCmd { get; }
@ -111,6 +116,26 @@ public class MainWindowViewModel : MyReactiveObject
{
await AddServerAsync(true, EConfigType.WireGuard);
});
AddAnytlsServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.Anytls);
});
AddBrookServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.Brook);
});
AddJuicityServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.Juicity);
});
AddNaiveServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.NaiveProxy);
});
AddShadowquicServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.Shadowquic);
});
AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(true, EConfigType.Custom);

View file

@ -1,6 +1,7 @@
using System.Reactive;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using ServiceLib.Models;
namespace ServiceLib.ViewModels;
@ -104,6 +105,21 @@ public class OptionSettingViewModel : MyReactiveObject
#endregion CoreType
#region SplitCoreType
[Reactive] public bool EnableSplitCore { get; set; }
[Reactive] public string SplitCoreType1 { get; set; }
[Reactive] public string SplitCoreType3 { get; set; }
[Reactive] public string SplitCoreType4 { get; set; }
[Reactive] public string SplitCoreType5 { get; set; }
[Reactive] public string SplitCoreType6 { get; set; }
[Reactive] public string SplitCoreType7 { get; set; }
[Reactive] public string SplitCoreType8 { get; set; }
[Reactive] public string SplitCoreType9 { get; set; }
[Reactive] public string RouteSplitCoreType { get; set; }
#endregion SplitCoreType
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
public OptionSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
@ -210,14 +226,13 @@ public class OptionSettingViewModel : MyReactiveObject
#endregion Tun mode
await InitCoreType();
await InitSplitCoreItem();
}
private async Task InitCoreType()
{
if (_config.CoreTypeItem == null)
{
_config.CoreTypeItem = new List<CoreTypeItem>();
}
_config.CoreTypeItem ??= new List<CoreTypeItem>();
foreach (EConfigType it in Enum.GetValues(typeof(EConfigType)))
{
@ -232,6 +247,7 @@ public class OptionSettingViewModel : MyReactiveObject
CoreType = ECoreType.Xray
});
}
_config.CoreTypeItem.ForEach(it =>
{
var type = it.CoreType.ToString();
@ -269,6 +285,87 @@ public class OptionSettingViewModel : MyReactiveObject
await Task.CompletedTask;
}
private async Task InitSplitCoreItem()
{
_config.SplitCoreItem ??= new()
{
EnableSplitCore = false,
SplitCoreTypes = new List<CoreTypeItem>(),
RouteCoreType = ECoreType.Xray
};
foreach (EConfigType it in Enum.GetValues(typeof(EConfigType)))
{
if (_config.SplitCoreItem.SplitCoreTypes.FindIndex(t => t.ConfigType == it) >= 0)
{
continue;
}
if (it is EConfigType.Hysteria2 or EConfigType.TUIC)
{
_config.SplitCoreItem.SplitCoreTypes.Add(new CoreTypeItem()
{
ConfigType = it,
CoreType = ECoreType.sing_box
});
continue;
}
if (it is EConfigType.Custom)
{
continue;
}
_config.SplitCoreItem.SplitCoreTypes.Add(new CoreTypeItem()
{
ConfigType = it,
CoreType = ECoreType.Xray
});
}
EnableSplitCore = _config.SplitCoreItem.EnableSplitCore;
RouteSplitCoreType = _config.SplitCoreItem.RouteCoreType.ToString();
_config.SplitCoreItem.SplitCoreTypes.ForEach(it =>
{
var type = it.CoreType.ToString();
switch ((int)it.ConfigType)
{
case 1:
SplitCoreType1 = type;
break;
case 3:
SplitCoreType3 = type;
break;
case 4:
SplitCoreType4 = type;
break;
case 5:
SplitCoreType5 = type;
break;
case 6:
SplitCoreType6 = type;
break;
case 7:
SplitCoreType7 = type;
break;
case 8:
SplitCoreType8 = type;
break;
case 9:
SplitCoreType9 = type;
break;
}
});
await Task.CompletedTask;
}
private async Task SaveSettingAsync()
{
if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString())
@ -362,6 +459,7 @@ public class OptionSettingViewModel : MyReactiveObject
//coreType
await SaveCoreType();
await SaveSplitCoreType();
if (await ConfigHandler.SaveConfig(_config) == 0)
{
@ -420,4 +518,46 @@ public class OptionSettingViewModel : MyReactiveObject
}
await Task.CompletedTask;
}
private async Task SaveSplitCoreType()
{
for (int k = 1; k <= _config.SplitCoreItem.SplitCoreTypes.Count; k++)
{
var item = _config.SplitCoreItem.SplitCoreTypes[k - 1];
var type = string.Empty;
switch ((int)item.ConfigType)
{
case 1:
type = SplitCoreType1;
break;
case 3:
type = SplitCoreType3;
break;
case 4:
type = SplitCoreType4;
break;
case 5:
type = SplitCoreType5;
break;
case 6:
type = SplitCoreType6;
break;
case 7:
type = SplitCoreType7;
break;
case 8:
type = SplitCoreType8;
break;
case 9:
type = SplitCoreType9;
break;
default:
continue;
}
item.CoreType = (ECoreType)Enum.Parse(typeof(ECoreType), type);
}
_config.SplitCoreItem.RouteCoreType = (ECoreType)Enum.Parse(typeof(ECoreType), RouteSplitCoreType);
_config.SplitCoreItem.EnableSplitCore = EnableSplitCore;
await Task.CompletedTask;
}
}

View file

@ -770,7 +770,8 @@ public class ProfilesViewModel : MyReactiveObject
}
if (blClipboard)
{
var result = await CoreConfigHandler.GenerateClientConfig(item, null);
var coreLaunchContext = new CoreLaunchContext(item, _config);
var result = await CoreConfigHandler.GenerateClientConfig(coreLaunchContext, null);
if (result.Success != true)
{
NoticeHandler.Instance.Enqueue(result.Msg);
@ -793,7 +794,8 @@ public class ProfilesViewModel : MyReactiveObject
{
return;
}
var result = await CoreConfigHandler.GenerateClientConfig(item, fileName);
var coreLaunchContext = new CoreLaunchContext(item, _config);
var result = await CoreConfigHandler.GenerateClientConfig(coreLaunchContext, fileName);
if (result.Success != true)
{
NoticeHandler.Instance.Enqueue(result.Msg);

View file

@ -533,6 +533,171 @@
HorizontalAlignment="Left"
Watermark="1500" />
</Grid>
<Grid
x:Name="gridAnytls"
Grid.Row="2"
ColumnDefinitions="180,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtId10"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
</Grid>
<Grid
x:Name="gridNaive"
Grid.Row="2"
ColumnDefinitions="180,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtId100"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbHeaderType100}" />
<ComboBox
x:Name="cmbHeaderType100"
Grid.Row="2"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
</Grid>
<Grid
x:Name="gridJuicity"
Grid.Row="2"
ColumnDefinitions="180,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId}" />
<TextBox
x:Name="txtId101"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtSecurity101"
Grid.Row="2"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbHeaderType8}" />
<ComboBox
x:Name="cmbHeaderType101"
Grid.Row="3"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
</Grid>
<Grid
x:Name="gridBrook"
Grid.Row="2"
ColumnDefinitions="180,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtId102"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
</Grid>
<Grid
x:Name="gridShadowquic"
Grid.Row="2"
ColumnDefinitions="180,Auto"
IsVisible="False"
RowDefinitions="Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId}" />
<TextBox
x:Name="txtId103"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtSecurity103"
Grid.Row="2"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbHeaderType8}" />
<ComboBox
x:Name="cmbHeaderType103"
Grid.Row="3"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
</Grid>
<Separator
x:Name="sepa2"

View file

@ -78,7 +78,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridHysteria2.IsVisible = true;
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
cmbCoreType.IsEnabled = false;
cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes.AppendEmpty();
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
break;
@ -87,11 +87,11 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridTuic.IsVisible = true;
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
cmbCoreType.IsEnabled = false;
cmbCoreType.ItemsSource = Global.TuicCoreTypes.AppendEmpty();
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
cmbHeaderType8.ItemsSource = Global.TuicCongestionControls;
cmbHeaderType8.ItemsSource = Global.CongestionControls;
break;
case EConfigType.WireGuard:
@ -102,6 +102,55 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridTls.IsVisible = false;
break;
case EConfigType.Anytls:
gridAnytls.IsVisible = true;
lstStreamSecurity.Add(Global.StreamSecurityReality);
cmbCoreType.IsEnabled = false;
break;
case EConfigType.NaiveProxy:
gridNaive.IsVisible = true;
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
cmbAlpn.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
cmbCoreType.IsEnabled = false;
cmbHeaderType100.ItemsSource = Global.NaiveProxyProtocols;
break;
case EConfigType.Juicity:
gridJuicity.IsVisible = true;
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
cmbAlpn.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
cmbCoreType.IsEnabled = false;
cmbHeaderType101.ItemsSource = Global.CongestionControls;
break;
case EConfigType.Brook:
gridBrook.IsVisible = true;
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
gridTls.IsVisible = false;
cmbCoreType.IsEnabled = false;
break;
case EConfigType.Shadowquic:
gridShadowquic.IsVisible = true;
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty;
cmbCoreType.IsEnabled = false;
cmbHeaderType103.ItemsSource = Global.CongestionControls;
break;
}
cmbStreamSecurity.ItemsSource = lstStreamSecurity;
@ -167,6 +216,31 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
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);
break;
case EConfigType.Anytls:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables);
break;
case EConfigType.NaiveProxy:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId100.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType100.SelectedValue).DisposeWith(disposables);
break;
case EConfigType.Juicity:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId101.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity101.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType101.SelectedValue).DisposeWith(disposables);
break;
case EConfigType.Brook:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId102.Text).DisposeWith(disposables);
break;
case EConfigType.Shadowquic:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId103.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity103.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType103.SelectedValue).DisposeWith(disposables);
break;
}
this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.SelectedValue).DisposeWith(disposables);

View file

@ -46,6 +46,12 @@
<Separator />
<MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
<MenuItem x:Name="menuAddTuicServer" Header="{x:Static resx:ResUI.menuAddTuicServer}" />
<MenuItem x:Name="menuAddAnytlsServer" Header="{x:Static resx:ResUI.menuAddAnytlsServer}" />
<Separator />
<MenuItem x:Name="menuAddBrookServer" Header="{x:Static resx:ResUI.menuAddBrookServer}" />
<MenuItem x:Name="menuAddJuicityServer" Header="{x:Static resx:ResUI.menuAddJuicityServer}" />
<MenuItem x:Name="menuAddNaiveServer" Header="{x:Static resx:ResUI.menuAddNaiveServer}" />
<MenuItem x:Name="menuAddShadowquicServer" Header="{x:Static resx:ResUI.menuAddShadowquicServer}" />
</MenuItem>
<MenuItem Padding="8,0">

View file

@ -83,6 +83,11 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddBrookServerCmd, v => v.menuAddBrookServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddJuicityServerCmd, v => v.menuAddJuicityServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddNaiveServerCmd, v => v.menuAddNaiveServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddShadowquicServerCmd, v => v.menuAddShadowquicServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);

View file

@ -802,101 +802,298 @@
</TabItem>
<TabItem Header="{x:Static resx:ResUI.TbSettingsCoreType}">
<Grid
Margin="{StaticResource Margin4}"
ColumnDefinitions="Auto,Auto"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="VMess" />
<ComboBox
x:Name="cmbCoreType1"
Grid.Row="1"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<ScrollViewer VerticalScrollBarVisibility="Visible">
<Grid Margin="{StaticResource Margin4}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Custom" />
<ComboBox
x:Name="cmbCoreType2"
Grid.Row="2"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<Grid
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource Margin4}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<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" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Shadowsocks" />
<ComboBox
x:Name="cmbCoreType3"
Grid.Row="3"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="VMess" />
<ComboBox
x:Name="cmbCoreType1"
Grid.Row="1"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Socks" />
<ComboBox
x:Name="cmbCoreType4"
Grid.Row="4"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Custom" />
<ComboBox
x:Name="cmbCoreType2"
Grid.Row="2"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="VLESS" />
<ComboBox
x:Name="cmbCoreType5"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Shadowsocks" />
<ComboBox
x:Name="cmbCoreType3"
Grid.Row="3"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Trojan" />
<ComboBox
x:Name="cmbCoreType6"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Socks" />
<ComboBox
x:Name="cmbCoreType4"
Grid.Row="4"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Wireguard" />
<ComboBox
x:Name="cmbCoreType9"
Grid.Row="7"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
</Grid>
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="VLESS" />
<ComboBox
x:Name="cmbCoreType5"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Trojan" />
<ComboBox
x:Name="cmbCoreType6"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Wireguard" />
<ComboBox
x:Name="cmbCoreType9"
Grid.Row="7"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
</Grid>
<Grid
Grid.Row="0"
Grid.Column="1"
Margin="{StaticResource Margin4}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<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" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsSplitCoreDoc1}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsSplitCoreDoc2}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsSplitCoreEnable}" />
<ToggleSwitch
x:Name="togCoreSplit"
Grid.Row="2"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="路由 Core" />
<ComboBox
x:Name="cmbCoreSplitRouteType"
Grid.Row="3"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="VMess" />
<ComboBox
x:Name="cmbCoreSplitType1"
Grid.Row="4"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Shadowsocks" />
<ComboBox
x:Name="cmbCoreSplitType3"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Socks" />
<ComboBox
x:Name="cmbCoreSplitType4"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="VLESS" />
<ComboBox
x:Name="cmbCoreSplitType5"
Grid.Row="7"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Trojan" />
<ComboBox
x:Name="cmbCoreSplitType6"
Grid.Row="8"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="9"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Hysteria2" />
<ComboBox
x:Name="cmbCoreSplitType7"
Grid.Row="9"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="TUIC" />
<ComboBox
x:Name="cmbCoreSplitType8"
Grid.Row="10"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="11"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="Wireguard" />
<ComboBox
x:Name="cmbCoreSplitType9"
Grid.Row="11"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
</Grid>
</Grid>
</ScrollViewer>
</TabItem>
</TabControl>
</DockPanel>

View file

@ -41,6 +41,17 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
cmbCoreType6.ItemsSource = Global.CoreTypes;
cmbCoreType9.ItemsSource = Global.CoreTypes;
cmbCoreSplitRouteType.ItemsSource = Global.CoreTypes;
cmbCoreSplitType1.ItemsSource = Global.CoreTypes;
cmbCoreSplitType3.ItemsSource = Global.CoreTypes;
cmbCoreSplitType4.ItemsSource = Global.CoreTypes;
cmbCoreSplitType5.ItemsSource = Global.CoreTypes;
cmbCoreSplitType6.ItemsSource = Global.CoreTypes;
cmbCoreSplitType7.ItemsSource = Global.Hysteria2CoreTypes;
cmbCoreSplitType8.ItemsSource = Global.TuicCoreTypes;
cmbCoreSplitType9.ItemsSource = Global.CoreTypes;
cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(2, 7).ToList();
cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).ToList();
cmbSpeedTestUrl.ItemsSource = Global.SpeedTestUrls;
@ -119,6 +130,18 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.CoreType6, v => v.cmbCoreType6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableSplitCore, v => v.togCoreSplit.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RouteSplitCoreType, v => v.cmbCoreSplitRouteType.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType1, v => v.cmbCoreSplitType1.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType3, v => v.cmbCoreSplitType3.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType4, v => v.cmbCoreSplitType4.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType5, v => v.cmbCoreSplitType5.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType6, v => v.cmbCoreSplitType6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType7, v => v.cmbCoreSplitType7.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType8, v => v.cmbCoreSplitType8.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType9, v => v.cmbCoreSplitType9.SelectedValue).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});

View file

@ -117,7 +117,7 @@ public partial class RoutingSettingWindow : WindowBase<RoutingSettingViewModel>
private void linkdomainStrategy4Singbox_Click(object? sender, RoutedEventArgs e)
{
ProcUtils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/shared/listen/#domain_strategy");
ProcUtils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/route/rule_action/#strategy");
}
private void btnCancel_Click(object? sender, RoutedEventArgs e)

View file

@ -707,6 +707,228 @@
materialDesign:HintAssist.Hint="1500"
Style="{StaticResource DefTextBox}" />
</Grid>
<Grid
x:Name="gridAnytls"
Grid.Row="2"
Visibility="Hidden">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtId10"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefTextBox}" />
</Grid>
<Grid
x:Name="gridNaive"
Grid.Row="2"
Visibility="Hidden">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtId100"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbHeaderType100}" />
<ComboBox
x:Name="cmbHeaderType100"
Grid.Row="3"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefComboBox}" />
</Grid>
<Grid
x:Name="gridJuicity"
Grid.Row="2"
Visibility="Hidden">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbId}" />
<TextBox
x:Name="txtId101"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtSecurity101"
Grid.Row="2"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbHeaderType8}" />
<ComboBox
x:Name="cmbHeaderType101"
Grid.Row="3"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefComboBox}" />
</Grid>
<Grid
x:Name="gridBrook"
Grid.Row="2"
Visibility="Hidden">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtId102"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefTextBox}" />
</Grid>
<Grid
x:Name="gridShadowquic"
Grid.Row="2"
Visibility="Hidden">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbId}" />
<TextBox
x:Name="txtId103"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtSecurity103"
Grid.Row="2"
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbHeaderType8}" />
<ComboBox
x:Name="cmbHeaderType103"
Grid.Row="3"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
Style="{StaticResource DefComboBox}" />
</Grid>
<Separator
x:Name="sepa2"

View file

@ -72,7 +72,7 @@ public partial class AddServerWindow
gridHysteria2.Visibility = Visibility.Visible;
sepa2.Visibility = Visibility.Collapsed;
gridTransport.Visibility = Visibility.Collapsed;
cmbCoreType.IsEnabled = false;
cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes.AppendEmpty();
cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty;
break;
@ -81,11 +81,11 @@ public partial class AddServerWindow
gridTuic.Visibility = Visibility.Visible;
sepa2.Visibility = Visibility.Collapsed;
gridTransport.Visibility = Visibility.Collapsed;
cmbCoreType.IsEnabled = false;
cmbCoreType.ItemsSource = Global.TuicCoreTypes.AppendEmpty();
cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty;
cmbHeaderType8.ItemsSource = Global.TuicCongestionControls;
cmbHeaderType8.ItemsSource = Global.CongestionControls;
break;
case EConfigType.WireGuard:
@ -96,6 +96,55 @@ public partial class AddServerWindow
gridTls.Visibility = Visibility.Collapsed;
break;
case EConfigType.Anytls:
gridAnytls.Visibility = Visibility.Visible;
cmbCoreType.IsEnabled = false;
lstStreamSecurity.Add(Global.StreamSecurityReality);
break;
case EConfigType.NaiveProxy:
gridNaive.Visibility = Visibility.Visible;
sepa2.Visibility = Visibility.Collapsed;
gridTransport.Visibility = Visibility.Collapsed;
cmbAlpn.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty;
cmbCoreType.IsEnabled = false;
cmbHeaderType100.ItemsSource = Global.NaiveProxyProtocols;
break;
case EConfigType.Juicity:
gridJuicity.Visibility = Visibility.Visible;
sepa2.Visibility = Visibility.Collapsed;
gridTransport.Visibility = Visibility.Collapsed;
cmbAlpn.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty;
cmbCoreType.IsEnabled = false;
cmbHeaderType101.ItemsSource = Global.CongestionControls;
break;
case EConfigType.Brook:
gridBrook.Visibility = Visibility.Visible;
sepa2.Visibility = Visibility.Collapsed;
gridTransport.Visibility = Visibility.Collapsed;
gridTls.Visibility = Visibility.Collapsed;
cmbCoreType.IsEnabled = false;
break;
case EConfigType.Shadowquic:
gridShadowquic.Visibility = Visibility.Visible;
sepa2.Visibility = Visibility.Collapsed;
gridTransport.Visibility = Visibility.Collapsed;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty;
cmbCoreType.IsEnabled = false;
cmbHeaderType103.ItemsSource = Global.CongestionControls;
break;
}
cmbStreamSecurity.ItemsSource = lstStreamSecurity;
@ -161,6 +210,31 @@ public partial class AddServerWindow
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);
break;
case EConfigType.Anytls:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables);
break;
case EConfigType.NaiveProxy:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId100.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType100.Text).DisposeWith(disposables);
break;
case EConfigType.Juicity:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId101.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity101.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType101.Text).DisposeWith(disposables);
break;
case EConfigType.Brook:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId102.Text).DisposeWith(disposables);
break;
case EConfigType.Shadowquic:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId103.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity103.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType103.Text).DisposeWith(disposables);
break;
}
this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.Text).DisposeWith(disposables);

View file

@ -108,6 +108,27 @@
x:Name="menuAddTuicServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddTuicServer}" />
<MenuItem
x:Name="menuAddAnytlsServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddAnytlsServer}" />
<Separator Margin="-40,5" />
<MenuItem
x:Name="menuAddBrookServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddBrookServer}" />
<MenuItem
x:Name="menuAddJuicityServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddJuicityServer}" />
<MenuItem
x:Name="menuAddNaiveServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddNaiveServer}" />
<MenuItem
x:Name="menuAddShadowquicServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddShadowquicServer}" />
</MenuItem>
</Menu>
<Separator />

View file

@ -80,6 +80,11 @@ public partial class MainWindow
this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddBrookServerCmd, v => v.menuAddBrookServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddJuicityServerCmd, v => v.menuAddJuicityServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddNaiveServerCmd, v => v.menuAddNaiveServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddShadowquicServerCmd, v => v.menuAddShadowquicServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);

View file

@ -1107,126 +1107,329 @@
</TabItem>
<TabItem Header="{x:Static resx:ResUI.TbSettingsCoreType}">
<Grid Margin="{StaticResource Margin8}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<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" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="VMess" />
<ComboBox
x:Name="cmbCoreType1"
Grid.Row="1"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<ScrollViewer VerticalScrollBarVisibility="Visible">
<Grid Margin="{StaticResource Margin8}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid
Grid.Row="0"
Grid.Column="0"
Margin="{StaticResource Margin8}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<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" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="VMess" />
<ComboBox
x:Name="cmbCoreType1"
Grid.Row="1"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Custom" />
<ComboBox
x:Name="cmbCoreType2"
Grid.Row="2"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Custom" />
<ComboBox
x:Name="cmbCoreType2"
Grid.Row="2"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Shadowsocks" />
<ComboBox
x:Name="cmbCoreType3"
Grid.Row="3"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Shadowsocks" />
<ComboBox
x:Name="cmbCoreType3"
Grid.Row="3"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Socks" />
<ComboBox
x:Name="cmbCoreType4"
Grid.Row="4"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Socks" />
<ComboBox
x:Name="cmbCoreType4"
Grid.Row="4"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="VLESS" />
<ComboBox
x:Name="cmbCoreType5"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="VLESS" />
<ComboBox
x:Name="cmbCoreType5"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Trojan" />
<ComboBox
x:Name="cmbCoreType6"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Trojan" />
<ComboBox
x:Name="cmbCoreType6"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Wireguard" />
<ComboBox
x:Name="cmbCoreType9"
Grid.Row="7"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
</Grid>
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Wireguard" />
<ComboBox
x:Name="cmbCoreType9"
Grid.Row="7"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
</Grid>
<Grid
Grid.Row="0"
Grid.Column="1"
Margin="{StaticResource Margin8}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<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" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsSplitCoreDoc1}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsSplitCoreDoc2}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsSplitCoreEnable}" />
<ToggleButton
x:Name="togCoreSplit"
Grid.Row="2"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="路由 Core" />
<ComboBox
x:Name="cmbCoreSplitRouteType"
Grid.Row="3"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="VMess" />
<ComboBox
x:Name="cmbCoreSplitType1"
Grid.Row="4"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Shadowsocks" />
<ComboBox
x:Name="cmbCoreSplitType3"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Socks" />
<ComboBox
x:Name="cmbCoreSplitType4"
Grid.Row="6"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="VLESS" />
<ComboBox
x:Name="cmbCoreSplitType5"
Grid.Row="7"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Trojan" />
<ComboBox
x:Name="cmbCoreSplitType6"
Grid.Row="8"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="9"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Hysteria2" />
<ComboBox
x:Name="cmbCoreSplitType7"
Grid.Row="9"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="TUIC" />
<ComboBox
x:Name="cmbCoreSplitType8"
Grid.Row="10"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="11"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="Wireguard" />
<ComboBox
x:Name="cmbCoreSplitType9"
Grid.Row="11"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" />
</Grid>
</Grid>
</ScrollViewer>
</TabItem>
</TabControl>
</DockPanel>

View file

@ -43,6 +43,17 @@ public partial class OptionSettingWindow
cmbCoreType6.ItemsSource = Global.CoreTypes;
cmbCoreType9.ItemsSource = Global.CoreTypes;
cmbCoreSplitRouteType.ItemsSource = Global.CoreTypes;
cmbCoreSplitType1.ItemsSource = Global.CoreTypes;
cmbCoreSplitType3.ItemsSource = Global.CoreTypes;
cmbCoreSplitType4.ItemsSource = Global.CoreTypes;
cmbCoreSplitType5.ItemsSource = Global.CoreTypes;
cmbCoreSplitType6.ItemsSource = Global.CoreTypes;
cmbCoreSplitType7.ItemsSource = Global.Hysteria2CoreTypes;
cmbCoreSplitType8.ItemsSource = Global.TuicCoreTypes;
cmbCoreSplitType9.ItemsSource = Global.CoreTypes;
cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(2, 7).ToList();
cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).ToList();
cmbSpeedTestUrl.ItemsSource = Global.SpeedTestUrls;
@ -131,6 +142,18 @@ public partial class OptionSettingWindow
this.Bind(ViewModel, vm => vm.CoreType6, v => v.cmbCoreType6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableSplitCore, v => v.togCoreSplit.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RouteSplitCoreType, v => v.cmbCoreSplitRouteType.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType1, v => v.cmbCoreSplitType1.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType3, v => v.cmbCoreSplitType3.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType4, v => v.cmbCoreSplitType4.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType5, v => v.cmbCoreSplitType5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType6, v => v.cmbCoreSplitType6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType7, v => v.cmbCoreSplitType7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType8, v => v.cmbCoreSplitType8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SplitCoreType9, v => v.cmbCoreSplitType9.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});
WindowsUtils.SetDarkBorder(this, AppHandler.Instance.Config.UiItem.CurrentTheme);

View file

@ -122,7 +122,7 @@ public partial class RoutingSettingWindow
private void linkdomainStrategy4Singbox_Click(object sender, RoutedEventArgs e)
{
ProcUtils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/shared/listen/#domain_strategy");
ProcUtils.ProcessStart("https://sing-box.sagernet.org/zh/configuration/route/rule_action/#strategy");
}
private void btnCancel_Click(object sender, System.Windows.RoutedEventArgs e)