Compare commits

...

27 commits

Author SHA1 Message Date
DHR60
bff32b017f
Merge a898c57518 into 7995bdd4df 2025-08-10 03:30:15 +00:00
DHR60
a898c57518 Fix custom config core type not working 2025-08-10 11:29:56 +08:00
DHR60
6905eb33f8 Fix hy2 custom config 2025-08-10 11:29:56 +08:00
DHR60
f737868b36 Fix missing hysteria2 arguments 2025-08-10 11:29:56 +08:00
DHR60
271fdbc2a8 Refactor 2025-08-10 11:29:56 +08:00
DHR60
c820b58067 Fixes Shadowquic Config Generate 2025-08-10 11:28:35 +08:00
DHR60
6319684592 Enhances core configuration handling. 2025-08-10 11:28:35 +08:00
DHR60
bf73e0c858 Fixes 2025-08-10 11:28:35 +08:00
DHR60
46688d04b3 Fixes hy2 2025-08-10 11:28:35 +08:00
DHR60
ade4cbcf80 Adds Xray and Singbox config type support 2025-08-10 11:28:35 +08:00
DHR60
8e177a9851 Add Naive etc. uri share
URI sharing is not compatible with other clients. Compatibility was not part of the original design and it is intended solely for internal sharing and copying within the application.
2025-08-10 11:28:35 +08:00
DHR60
37a5b7f3b4 Fixes NaiveProxy 2025-08-10 11:28:35 +08:00
DHR60
6e750b4c03 avalonia 2025-08-10 11:28:35 +08:00
DHR60
59e7a5cdb9 Try add GUI support 2025-08-10 11:28:35 +08:00
DHR60
8f00d69e9d Fixes 2025-08-10 11:28:35 +08:00
DHR60
85b6e5ba50 ShowClashUI 2025-08-10 11:28:35 +08:00
DHR60
e156aaae34 Fixes (Prioritize Node.CoreType over SplitCore configuration) 2025-08-10 11:28:35 +08:00
DHR60
ac4692c7d0 Fixes SplitCore == false and Node.CoreType == ECoreType.hysteria2 etc. logic 2025-08-10 11:28:35 +08:00
DHR60
2035a5ae61 Fixes 2025-08-10 11:28:34 +08:00
DHR60
896ec62232 Enable Tun SplitCore 2025-08-10 11:28:34 +08:00
DHR60
0a2b4374f9 Fixes 2025-08-10 11:28:34 +08:00
DHR60
b8dbe61db3 Config Handler 2025-08-10 11:28:34 +08:00
DHR60
c899d534af Config and GUI 2025-08-10 11:28:34 +08:00
DHR60
b460b0ff91 Add Minimal Config Type 2025-08-10 11:28:34 +08:00
DHR60
ec00037526 Add Minimal Core Config 2025-08-10 11:28:34 +08:00
DHR60
7995bdd4df
Migrate to sing-box 1.12 support (#7521)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
* Revert "Temporary addition to support proper use of sing-box v1.12"

This reverts commit 508eb24fc3.

* Migrating to singbox 1.11 support

* Removes unnecessary sniffer

* Migrating to singbox 1.12 support

* Adds Google cn dns rules

* Improves geoip rule handling in singbox

* add anytls support

* Simplifies local DNS address handling

* Enables dhcp interface configuration

* Fetches DNS strategy for domain resolution

* support Wireguard endpoint
Refactors Singbox config classes for dial fields

* Utils.GetFreePort() default port to be zero

* Adds Sing-box legacy DNS config support

* Adds IPv4 preference to DNS configurations

对应原dns.servers[].strategy = prefer_ipv4

* Refactors DNS address parsing

* Fixes config generation

* fix singbox endpoints proxy chain not work

* Fixes wrong field

* Removes direct clash_mode domain strategy

* Improves DNS address parsing in Singbox

DNS type, host, port, and path

* Adds properties to Rule4Sbox class

* Removes Wireguard listen port

* Support sing-box hosts

* Adds tag resolver supports

* Adds sing-box DomainStrategy support

* Deletes Duplicate Rules

* Adds anytls reality support

* Fixes

* Updates sing-box documentation link

* Updates translations
2025-08-10 10:15:32 +08:00
2dust
df95cc6af7 Code clean 2025-08-10 09:17:15 +08:00
63 changed files with 3975 additions and 605 deletions

View file

@ -411,11 +411,11 @@ public class Utils
// Link-local address fe80::/10
if (ipBytes[0] == 0xfe && (ipBytes[1] & 0xc0) == 0x80)
return true;
// Unique local address fc00::/7 (typically fd00::/8)
if ((ipBytes[0] & 0xfe) == 0xfc)
return true;
// Private portion in IPv4-mapped addresses ::ffff:0:0/96
if (address.IsIPv4MappedToIPv6)
{
@ -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,
};
@ -1997,7 +2144,7 @@ public class ConfigHandler
if (!blImportAdvancedRules && items.Count > 0)
{
//migrate
//migrate
//TODO Temporary code to be removed later
if (config.RoutingBasicItem.RoutingIndexId.IsNotEmpty())
{

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);
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, true);
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);
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

@ -62,7 +62,7 @@ public class BaseFmt
if (item.Mldsa65Verify.IsNotEmpty())
{
dicQuery.Add("pqv", Utils.UrlEncode(item.Mldsa65Verify));
}
}
if (item.AllowInsecure.Equals("true"))
{
dicQuery.Add("allowInsecure", "1");

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

@ -234,7 +234,7 @@ public class ClashProxiesViewModel : MyReactiveObject
else
{
SelectedGroup = _proxyGroups.First();
}
}
}
else
{

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

@ -73,6 +73,7 @@
<ScrollViewer x:Name="msgScrollViewer" VerticalScrollBarVisibility="Auto">
<SelectableTextBlock
Name="txtMsg"
Margin="{StaticResource Margin8}"
VerticalAlignment="Stretch"
Classes="TextArea"
TextAlignment="Left"

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

@ -25,7 +25,7 @@ public partial class MsgView
menuMsgViewCopy.Click += menuMsgViewCopy_Click;
menuMsgViewCopyAll.Click += menuMsgViewCopyAll_Click;
menuMsgViewClear.Click += menuMsgViewClear_Click;
cmbMsgFilter.ItemsSource = Global.PresetMsgFilters;
}

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)