Compare commits

...

55 commits

Author SHA1 Message Date
DHR60
88c2246c4c
Merge 739a5aeaa2 into c669e72189 2025-08-07 10:42:41 +00:00
DHR60
739a5aeaa2 Fix custom config core type not working 2025-08-07 17:48:08 +08:00
DHR60
50dcae9a04 Fix hy2 custom config 2025-08-07 17:48:08 +08:00
DHR60
2910f4838e Fix missing hysteria2 arguments 2025-08-07 17:48:07 +08:00
DHR60
bee0521e47 Refactor 2025-08-07 17:48:07 +08:00
DHR60
15c3eea92e Fixes Shadowquic Config Generate 2025-08-07 17:48:07 +08:00
DHR60
147e837bbb Enhances core configuration handling. 2025-08-07 17:48:07 +08:00
DHR60
35f9e4bca0 Fixes 2025-08-07 17:48:07 +08:00
DHR60
8ed5c575fc Fixes hy2 2025-08-07 17:48:07 +08:00
DHR60
f6b6926419 Adds Xray and Singbox config type support 2025-08-07 17:48:07 +08:00
DHR60
3298286f01 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-07 17:48:07 +08:00
DHR60
8ec94fe812 Fixes NaiveProxy 2025-08-07 17:48:07 +08:00
DHR60
372e832651 avalonia 2025-08-07 17:48:07 +08:00
DHR60
3e322b4d68 Try add GUI support 2025-08-07 17:48:07 +08:00
DHR60
2b46857763 Fixes 2025-08-07 17:48:07 +08:00
DHR60
94afd9969a ShowClashUI 2025-08-07 17:48:07 +08:00
DHR60
db708d1e5d Fixes (Prioritize Node.CoreType over SplitCore configuration) 2025-08-07 17:48:07 +08:00
DHR60
6462fa0905 Fixes SplitCore == false and Node.CoreType == ECoreType.hysteria2 etc. logic 2025-08-07 17:48:07 +08:00
DHR60
8deb897f55 Fixes 2025-08-07 17:48:07 +08:00
DHR60
e28f5ce9c2 Enable Tun SplitCore 2025-08-07 17:48:07 +08:00
DHR60
45a2ff118e Fixes 2025-08-07 17:48:07 +08:00
DHR60
daf59d01dc Config Handler 2025-08-07 17:48:07 +08:00
DHR60
815a9ce0db Config and GUI 2025-08-07 17:48:07 +08:00
DHR60
edbd8ab54a Add Minimal Config Type 2025-08-07 17:48:07 +08:00
DHR60
d2169c914a Add Minimal Core Config 2025-08-07 17:48:07 +08:00
DHR60
47779e433d Updates translations 2025-08-07 17:38:00 +08:00
DHR60
c5f222bf58 Updates sing-box documentation link 2025-08-07 17:38:00 +08:00
DHR60
1945d9b798 Fixes 2025-08-07 17:38:00 +08:00
DHR60
2e283eb00e Adds anytls reality support 2025-08-07 17:38:00 +08:00
DHR60
00505c7c17 Deletes Duplicate Rules 2025-08-07 17:38:00 +08:00
DHR60
9229b491c5 Adds sing-box DomainStrategy support 2025-08-07 17:38:00 +08:00
DHR60
d9b0aff8da Adds tag resolver supports 2025-08-07 17:38:00 +08:00
DHR60
8ea39d7475 Support sing-box hosts 2025-08-07 17:38:00 +08:00
DHR60
f14f39f5a1 Removes Wireguard listen port 2025-08-07 17:38:00 +08:00
DHR60
228069f499 Adds properties to Rule4Sbox class 2025-08-07 17:38:00 +08:00
DHR60
645e4e7711 Improves DNS address parsing in Singbox
DNS type, host, port, and path
2025-08-07 17:38:00 +08:00
DHR60
d1c16ecb72 Removes direct clash_mode domain strategy 2025-08-07 17:38:00 +08:00
DHR60
dca737bdad Fixes wrong field 2025-08-07 17:38:00 +08:00
DHR60
7f5489c5f7 fix singbox endpoints proxy chain not work 2025-08-07 17:38:00 +08:00
DHR60
3be0b312d6 Fixes config generation 2025-08-07 17:38:00 +08:00
DHR60
b290a38ef4 Refactors DNS address parsing 2025-08-07 17:38:00 +08:00
DHR60
70bdb3de16 Adds IPv4 preference to DNS configurations
对应原dns.servers[].strategy = prefer_ipv4
2025-08-07 17:38:00 +08:00
DHR60
8f9d1951a2 Adds Sing-box legacy DNS config support 2025-08-07 17:38:00 +08:00
DHR60
e2faf61a8b Utils.GetFreePort() default port to be zero 2025-08-07 17:38:00 +08:00
DHR60
3824879d38 support Wireguard endpoint
Refactors Singbox config classes for dial fields
2025-08-07 17:38:00 +08:00
DHR60
d54433aeb3 Fetches DNS strategy for domain resolution 2025-08-07 17:37:59 +08:00
DHR60
26b2240779 Enables dhcp interface configuration 2025-08-07 17:37:59 +08:00
DHR60
98a5caa47f Simplifies local DNS address handling 2025-08-07 17:37:59 +08:00
DHR60
d77c25aef5 add anytls support 2025-08-07 17:37:59 +08:00
DHR60
7affcf97b1 Improves geoip rule handling in singbox 2025-08-07 17:37:59 +08:00
DHR60
9e1e5eb2aa Adds Google cn dns rules 2025-08-07 17:37:59 +08:00
DHR60
d1928d80c7 Migrating to singbox 1.12 support 2025-08-07 17:37:59 +08:00
DHR60
ea55dfb6c5 Removes unnecessary sniffer 2025-08-07 17:37:59 +08:00
DHR60
b3acb89c29 Migrating to singbox 1.11 support 2025-08-07 17:37:59 +08:00
DHR60
922cf54d93 Revert "Temporary addition to support proper use of sing-box v1.12"
This reverts commit 508eb24fc3.
2025-08-07 17:37:48 +08:00
59 changed files with 3968 additions and 599 deletions

View file

@ -466,11 +466,11 @@ public class Utils
return false; return false;
} }
public static int GetFreePort(int defaultPort = 9090) public static int GetFreePort(int defaultPort = 0)
{ {
try try
{ {
if (!Utils.PortInUse(defaultPort)) if (!(defaultPort == 0 || Utils.PortInUse(defaultPort)))
{ {
return 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"); var tempPath = Path.Combine(StartupPath(), "binConfigs");
if (!Directory.Exists(tempPath)) if (!Directory.Exists(tempPath))
@ -827,10 +827,27 @@ public class Utils
} }
else 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 #endregion TempPath
#region Platform #region Platform

View file

@ -11,5 +11,10 @@ public enum EConfigType
Hysteria2 = 7, Hysteria2 = 7,
TUIC = 8, TUIC = 8,
WireGuard = 9, 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, api,
api2, api2,
mixed, mixed,
split,
speedtest = 21 speedtest = 21
} }

View file

@ -12,8 +12,8 @@ public class Global
public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="; public const string PromotionUrl = @"aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw=";
public const string ConfigFileName = "guiNConfig.json"; public const string ConfigFileName = "guiNConfig.json";
public const string CoreConfigFileName = "config.json"; public const string CoreConfigFileName = "config";
public const string CorePreConfigFileName = "configPre.json"; public const string CorePreConfigFileName = "configPre";
public const string CoreSpeedtestConfigFileName = "configTest{0}.json"; public const string CoreSpeedtestConfigFileName = "configTest{0}.json";
public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json"; public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json";
public const string ClashMixinConfigFileName = "Mixin.yaml"; public const string ClashMixinConfigFileName = "Mixin.yaml";
@ -169,7 +169,12 @@ public class Global
{ EConfigType.Trojan, "trojan://" }, { EConfigType.Trojan, "trojan://" },
{ EConfigType.Hysteria2, "hysteria2://" }, { EConfigType.Hysteria2, "hysteria2://" },
{ EConfigType.TUIC, "tuic://" }, { 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() public static readonly Dictionary<EConfigType, string> ProtocolTypes = new()
@ -182,7 +187,12 @@ public class Global
{ EConfigType.Trojan, "trojan" }, { EConfigType.Trojan, "trojan" },
{ EConfigType.Hysteria2, "hysteria2" }, { EConfigType.Hysteria2, "hysteria2" },
{ EConfigType.TUIC, "tuic" }, { 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 = public static readonly List<string> VmessSecurities =
@ -276,6 +286,75 @@ public class Global
"sing_box" "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 = public static readonly List<string> DomainStrategies =
[ [
"AsIs", "AsIs",
@ -462,13 +541,20 @@ public class Global
"" ""
]; ];
public static readonly List<string> TuicCongestionControls = public static readonly List<string> CongestionControls =
[ [
"cubic", "cubic",
"new_reno", "new_reno",
"bbr" "bbr"
]; ];
public static readonly List<string> NaiveProxyProtocols =
[
"https",
"http",
"quic"
];
public static readonly List<string> allowSelectType = public static readonly List<string> allowSelectType =
[ [
"selector", "selector",

View file

@ -1,3 +1,7 @@
using DynamicData;
using ServiceLib.Enums;
using ServiceLib.Models;
namespace ServiceLib.Handler; namespace ServiceLib.Handler;
public sealed class AppHandler public sealed class AppHandler
@ -231,9 +235,89 @@ public sealed class AppHandler
return (ECoreType)profileItem.CoreType; return (ECoreType)profileItem.CoreType;
} }
return GetCoreType(eConfigType);
}
public ECoreType GetCoreType(EConfigType eConfigType)
{
var item = _config.CoreTypeItem?.FirstOrDefault(it => it.ConfigType == eConfigType); var item = _config.CoreTypeItem?.FirstOrDefault(it => it.ConfigType == eConfigType);
return item?.CoreType ?? ECoreType.Xray; 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 #endregion Core Type
} }

View file

@ -165,6 +165,13 @@ public class ConfigHandler
config.SystemProxyItem.SystemProxyExceptions = Utils.IsWindows() ? Global.SystemProxyExceptionsWindows : Global.SystemProxyExceptionsLinux; config.SystemProxyItem.SystemProxyExceptions = Utils.IsWindows() ? Global.SystemProxyExceptionsWindows : Global.SystemProxyExceptionsLinux;
} }
config.SplitCoreItem ??= new()
{
EnableSplitCore = false,
SplitCoreTypes = new List<CoreTypeItem>(),
RouteCoreType = ECoreType.Xray
};
return config; return config;
} }
@ -262,6 +269,11 @@ public class ConfigHandler
EConfigType.Hysteria2 => await AddHysteria2Server(config, item), EConfigType.Hysteria2 => await AddHysteria2Server(config, item),
EConfigType.TUIC => await AddTuicServer(config, item), EConfigType.TUIC => await AddTuicServer(config, item),
EConfigType.WireGuard => await AddWireguardServer(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, _ => -1,
}; };
return ret; return ret;
@ -690,7 +702,7 @@ public class ConfigHandler
public static async Task<int> AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true) public static async Task<int> AddHysteria2Server(Config config, ProfileItem profileItem, bool toFile = true)
{ {
profileItem.ConfigType = EConfigType.Hysteria2; profileItem.ConfigType = EConfigType.Hysteria2;
profileItem.CoreType = ECoreType.sing_box; //profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx(); profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.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) public static async Task<int> AddTuicServer(Config config, ProfileItem profileItem, bool toFile = true)
{ {
profileItem.ConfigType = EConfigType.TUIC; profileItem.ConfigType = EConfigType.TUIC;
profileItem.CoreType = ECoreType.sing_box; //profileItem.CoreType = ECoreType.sing_box;
profileItem.Address = profileItem.Address.TrimEx(); profileItem.Address = profileItem.Address.TrimEx();
profileItem.Id = profileItem.Id.TrimEx(); profileItem.Id = profileItem.Id.TrimEx();
profileItem.Security = profileItem.Security.TrimEx(); profileItem.Security = profileItem.Security.TrimEx();
profileItem.Network = string.Empty; 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()) if (profileItem.StreamSecurity.IsNullOrEmpty())
@ -786,6 +798,173 @@ public class ConfigHandler
return 0; 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> /// <summary>
/// Sort the server list by the specified column /// Sort the server list by the specified column
/// Updates the sort order in the profile extension data /// Updates the sort order in the profile extension data
@ -1160,43 +1339,6 @@ public class ConfigHandler
return result; 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> /// <summary>
/// Remove servers with invalid test results (timeout) /// Remove servers with invalid test results (timeout)
/// Useful for cleaning up subscription lists /// Useful for cleaning up subscription lists
@ -1295,6 +1437,11 @@ public class ConfigHandler
EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false), EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false),
EConfigType.TUIC => await AddTuicServer(config, profileItem, false), EConfigType.TUIC => await AddTuicServer(config, profileItem, false),
EConfigType.WireGuard => await AddWireguardServer(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, _ => -1,
}; };

View file

@ -35,7 +35,7 @@ public class CoreAdminHandler
{ {
StringBuilder sb = new(); StringBuilder sb = new();
sb.AppendLine("#!/bin/bash"); 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}"); sb.AppendLine($"sudo -S {cmdLine}");
var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true); 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; namespace ServiceLib.Handler;
/// <summary> /// <summary>
@ -7,27 +9,26 @@ public class CoreConfigHandler
{ {
private static readonly string _tag = "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(); var result = new RetResult();
if (node.ConfigType == EConfigType.Custom) if (context.ConfigType == EConfigType.Custom)
{ {
result = node.CoreType switch result = await GetCoreConfigServiceForCustom(context.CoreType).GenerateClientCustomConfig(context.Node, fileName);
{
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);
} }
else 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) if (result.Success != true)
{ {
@ -41,65 +42,44 @@ public class CoreConfigHandler
return result; 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 try
{ {
if (node == null || fileName is null) result = await GetCoreConfigServiceForPassthrough(context.CoreType).GeneratePassthroughConfig(context.Node);
{
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) catch (Exception ex)
{ {
Logging.SaveLog(_tag, ex); Logging.SaveLog(_tag, ex);
ret.Msg = ResUI.FailedGenDefaultConfiguration; result.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; 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) public static async Task<RetResult> GenerateClientSpeedtestConfig(Config config, string fileName, List<ServerTestItem> selecteds, ECoreType coreType)
{ {
var result = new RetResult(); 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) if (result.Success != true)
{ {
@ -109,21 +89,24 @@ public class CoreConfigHandler
return result; 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 result = new RetResult();
var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest); var initPort = AppHandler.Instance.GetLocalPort(EInboundProtocol.speedtest);
var port = Utils.GetFreePort(initPort + testItem.QueueNum); var port = Utils.GetFreePort(initPort + testItem.QueueNum);
testItem.Port = port; 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) if (result.Success != true)
{ {
return result; return result;
@ -140,7 +123,7 @@ public class CoreConfigHandler
{ {
result = await new CoreConfigSingboxService(config).GenerateClientMultipleLoadConfig(selecteds); result = await new CoreConfigSingboxService(config).GenerateClientMultipleLoadConfig(selecteds);
} }
else else if (coreType == ECoreType.Xray)
{ {
result = await new CoreConfigV2rayService(config).GenerateClientMultipleLoadConfig(selecteds, multipleLoad); result = await new CoreConfigV2rayService(config).GenerateClientMultipleLoadConfig(selecteds, multipleLoad);
} }
@ -152,4 +135,96 @@ public class CoreConfigHandler
await File.WriteAllTextAsync(fileName, result.Data.ToString()); await File.WriteAllTextAsync(fileName, result.Data.ToString());
return result; 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.Diagnostics;
using System.Text; using System.Text;
using ServiceLib.Enums;
using ServiceLib.Models;
using static SQLite.SQLite3;
namespace ServiceLib.Handler; namespace ServiceLib.Handler;
@ -25,8 +28,6 @@ public class CoreHandler
Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable(Global.XrayLocalCert, 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) //Copy the bin folder to the storage location (for init)
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1") if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
@ -73,28 +74,23 @@ public class CoreHandler
return; return;
} }
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); // Create launch context and configure parameters
var result = await CoreConfigHandler.GenerateClientConfig(node, fileName); var context = new CoreLaunchContext(node, _config);
if (result.Success != true) context.AdjustForConfigType();
// Start main core
if (!await CoreStart(context))
{ {
UpdateFunc(true, result.Msg);
return; return;
} }
UpdateFunc(false, $"{node.GetSummary()}"); // Start pre-core if needed
UpdateFunc(false, $"{Utils.GetRuntimeInfo()}"); if (!await CoreStartPreService(context))
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 CoreStop(); // Clean up main core if pre-core fails
await WindowsUtils.RemoveTunDevice(); return;
} }
await CoreStart(node);
await CoreStartPreService(node);
if (_process != null) if (_process != null)
{ {
UpdateFunc(true, $"{node.GetSummary()}"); UpdateFunc(true, $"{node.GetSummary()}");
@ -103,9 +99,9 @@ public class CoreHandler
public async Task<int> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds) 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 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); var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);
UpdateFunc(false, result.Msg); UpdateFunc(false, result.Msg);
if (result.Success != true) if (result.Success != true)
@ -134,15 +130,17 @@ public class CoreHandler
return -1; return -1;
} }
var context = new CoreLaunchContext(node, _config);
context.AdjustForConfigType();
var coreType = context.CoreType;
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); 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, node, testItem, configPath); var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, context, testItem, configPath);
if (result.Success != true) if (result.Success != true)
{ {
return -1; return -1;
} }
var coreType = AppHandler.Instance.GetCoreType(node, node.ConfigType);
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType); var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(coreType);
var proc = await RunProcess(coreInfo, fileName, true, false); var proc = await RunProcess(coreInfo, fileName, true, false);
if (proc is null) if (proc is null)
@ -183,43 +181,84 @@ public class CoreHandler
#region Private #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 coreType = context.SplitCore ? context.PureEndpointCore : context.CoreType;
var coreInfo = CoreInfoHandler.Instance.GetCoreInfo(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; if (result.Success != true)
var proc = await RunProcess(coreInfo, Global.CoreConfigFileName, displayLog, 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) if (proc is null)
{ {
return; UpdateFunc(true, ResUI.FailedToRunCore);
return false;
} }
_process = proc; _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); return true; // No pre-core needed, consider successful
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;
}
}
} }
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) private void UpdateFunc(bool notify, string msg)
@ -269,7 +308,7 @@ public class CoreHandler
StartInfo = new() StartInfo = new()
{ {
FileName = fileName, 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(), WorkingDirectory = Utils.GetBinConfigPath(),
UseShellExecute = false, UseShellExecute = false,
RedirectStandardOutput = displayLog, RedirectStandardOutput = displayLog,

View file

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

View file

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

View file

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

View file

@ -18,6 +18,11 @@ public class FmtHandler
EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item), EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item),
EConfigType.TUIC => TuicFmt.ToUri(item), EConfigType.TUIC => TuicFmt.ToUri(item),
EConfigType.WireGuard => WireguardFmt.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, _ => null,
}; };
@ -75,6 +80,26 @@ public class FmtHandler
{ {
return WireguardFmt.Resolve(str, out msg); 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 else
{ {
msg = ResUI.NonvmessOrssProtocol; 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<InItem> Inbound { get; set; }
public List<KeyEventItem> GlobalHotkeys { get; set; } public List<KeyEventItem> GlobalHotkeys { get; set; }
public List<CoreTypeItem> CoreTypeItem { get; set; } public List<CoreTypeItem> CoreTypeItem { get; set; }
public SplitCoreItem SplitCoreItem { get; set; }
#endregion other entities #endregion other entities
} }

View file

@ -138,6 +138,14 @@ public class CoreTypeItem
public ECoreType CoreType { get; set; } 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] [Serializable]
public class TunModeItem 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; namespace ServiceLib.Models;
public class SingboxConfig public class SingboxConfig
@ -6,6 +8,7 @@ public class SingboxConfig
public Dns4Sbox? dns { get; set; } public Dns4Sbox? dns { get; set; }
public List<Inbound4Sbox> inbounds { get; set; } public List<Inbound4Sbox> inbounds { get; set; }
public List<Outbound4Sbox> outbounds { get; set; } public List<Outbound4Sbox> outbounds { get; set; }
public List<Endpoints4Sbox>? endpoints { get; set; }
public Route4Sbox route { get; set; } public Route4Sbox route { get; set; }
public Experimental4Sbox? experimental { get; set; } public Experimental4Sbox? experimental { get; set; }
} }
@ -29,7 +32,6 @@ public class Dns4Sbox
public bool? independent_cache { get; set; } public bool? independent_cache { get; set; }
public bool? reverse_mapping { get; set; } public bool? reverse_mapping { get; set; }
public string? client_subnet { get; set; } public string? client_subnet { get; set; }
public Fakeip4Sbox? fakeip { get; set; }
} }
public class Route4Sbox public class Route4Sbox
@ -37,6 +39,7 @@ public class Route4Sbox
public bool? auto_detect_interface { get; set; } public bool? auto_detect_interface { get; set; }
public List<Rule4Sbox> rules { get; set; } public List<Rule4Sbox> rules { get; set; }
public List<Ruleset4Sbox>? rule_set { get; set; } public List<Ruleset4Sbox>? rule_set { get; set; }
public string? final { get; set; }
} }
[Serializable] [Serializable]
@ -49,6 +52,7 @@ public class Rule4Sbox
public string? mode { get; set; } public string? mode { get; set; }
public bool? ip_is_private { get; set; } public bool? ip_is_private { get; set; }
public string? client_subnet { get; set; } public string? client_subnet { get; set; }
public int? rewrite_ttl { get; set; }
public bool? invert { get; set; } public bool? invert { get; set; }
public string? clash_mode { get; set; } public string? clash_mode { get; set; }
public List<string>? inbound { 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>? process_name { get; set; }
public List<string>? rule_set { get; set; } public List<string>? rule_set { get; set; }
public List<Rule4Sbox>? rules { 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] [Serializable]
@ -76,7 +101,6 @@ public class Inbound4Sbox
public string tag { get; set; } public string tag { get; set; }
public string listen { get; set; } public string listen { get; set; }
public int? listen_port { get; set; } public int? listen_port { get; set; }
public string? domain_strategy { get; set; }
public string interface_name { get; set; } public string interface_name { get; set; }
public List<string>? address { get; set; } public List<string>? address { get; set; }
public int? mtu { get; set; } public int? mtu { get; set; }
@ -84,8 +108,6 @@ public class Inbound4Sbox
public bool? strict_route { get; set; } public bool? strict_route { get; set; }
public bool? endpoint_independent_nat { get; set; } public bool? endpoint_independent_nat { get; set; }
public string? stack { 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; } public List<User4Sbox> users { get; set; }
} }
@ -95,10 +117,8 @@ public class User4Sbox
public string password { get; set; } 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 string? server { get; set; }
public int? server_port { get; set; } public int? server_port { get; set; }
public List<string>? server_ports { 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_conn { get; set; }
public int? recv_window { get; set; } public int? recv_window { get; set; }
public bool? disable_mtu_discovery { get; set; } public bool? disable_mtu_discovery { get; set; }
public string? detour { get; set; }
public string? method { get; set; } public string? method { get; set; }
public string? username { get; set; } public string? username { get; set; }
public string? password { get; set; } public string? password { get; set; }
@ -121,21 +140,36 @@ public class Outbound4Sbox
public string? version { get; set; } public string? version { get; set; }
public string? network { get; set; } public string? network { get; set; }
public string? packet_encoding { 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 { get; set; }
public string? plugin_opts { 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 List<string>? outbounds { get; set; }
public bool? interrupt_exist_connections { 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 class Tls4Sbox
{ {
public bool enabled { get; set; } public bool enabled { get; set; }
@ -191,15 +225,25 @@ public class HyObfs4Sbox
public string? password { get; set; } 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 { get; set; }
public string? address_resolver { get; set; } public string? address_resolver { get; set; }
public string? address_strategy { get; set; } public string? address_strategy { get; set; }
public string? strategy { get; set; } public string? strategy { get; set; }
public string? detour { get; set; } // Deprecated End
public string? client_subnet { get; set; }
} }
public class Experimental4Sbox public class Experimental4Sbox
@ -229,13 +273,6 @@ public class Stats4Sbox
public List<string>? users { get; set; } 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 class CacheFile4Sbox
{ {
public bool enabled { get; set; } public bool enabled { get; set; }
@ -254,3 +291,33 @@ public class Ruleset4Sbox
public string? download_detour { get; set; } public string? download_detour { get; set; }
public string? update_interval { 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> /// <summary>
/// 查找类似 Add a custom configuration Configuration 的本地化字符串。 /// 查找类似 Add a custom configuration Configuration 的本地化字符串。
/// </summary> /// </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> /// <summary>
/// 查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。 /// 查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。
/// </summary> /// </summary>
@ -708,6 +744,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Add [Shadowquic] Configuration 的本地化字符串。
/// </summary>
public static string menuAddShadowquicServer {
get {
return ResourceManager.GetString("menuAddShadowquicServer", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Add [Shadowsocks] Configuration 的本地化字符串。 /// 查找类似 Add [Shadowsocks] Configuration 的本地化字符串。
/// </summary> /// </summary>
@ -2463,6 +2508,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Proxy Protocol 的本地化字符串。
/// </summary>
public static string TbHeaderType100 {
get {
return ResourceManager.GetString("TbHeaderType100", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Congestion control 的本地化字符串。 /// 查找类似 Congestion control 的本地化字符串。
/// </summary> /// </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> /// <summary>
/// 查找类似 sing-box ruleset files source (optional) 的本地化字符串。 /// 查找类似 sing-box ruleset files source (optional) 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1401,4 +1401,31 @@
<data name="TbMldsa65Verify" xml:space="preserve"> <data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value> <value>Mldsa65Verify</value>
</data> </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> </root>

View file

@ -1401,4 +1401,31 @@
<data name="TbMldsa65Verify" xml:space="preserve"> <data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value> <value>Mldsa65Verify</value>
</data> </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> </root>

View file

@ -1401,4 +1401,31 @@
<data name="TbMldsa65Verify" xml:space="preserve"> <data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value> <value>Mldsa65Verify</value>
</data> </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> </root>

View file

@ -1401,4 +1401,31 @@
<data name="TbMldsa65Verify" xml:space="preserve"> <data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value> <value>Mldsa65Verify</value>
</data> </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> </root>

View file

@ -1398,4 +1398,31 @@
<data name="TbMldsa65Verify" xml:space="preserve"> <data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value> <value>Mldsa65Verify</value>
</data> </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> </root>

View file

@ -1398,4 +1398,31 @@
<data name="TbMldsa65Verify" xml:space="preserve"> <data name="TbMldsa65Verify" xml:space="preserve">
<value>Mldsa65Verify</value> <value>Mldsa65Verify</value>
</data> </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> </root>

View file

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

View file

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

View file

@ -2,29 +2,33 @@
"servers": [ "servers": [
{ {
"tag": "remote", "tag": "remote",
"address": "tcp://8.8.8.8", "type": "tcp",
"strategy": "prefer_ipv4", "server": "8.8.8.8",
"detour": "proxy" "detour": "proxy"
}, },
{ {
"tag": "local", "tag": "local",
"address": "223.5.5.5", "type": "udp",
"strategy": "prefer_ipv4", "server": "223.5.5.5"
"detour": "direct"
},
{
"tag": "block",
"address": "rcode://success"
} }
], ],
"rules": [ "rules": [
{ {
"rule_set": [ "domain_suffix": [
"geosite-cn", "googleapis.cn",
"geosite-geolocation-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, 139,
5353 5353
], ],
"outbound": "block" "action": "reject"
}, },
{ {
"ip_cidr": [ "ip_cidr": [
"224.0.0.0/3", "224.0.0.0/3",
"ff00::/8" "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.Data;
using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Reactive;
using System.Text.Json.Nodes;
using DynamicData;
using ServiceLib.Models;
namespace ServiceLib.Services.CoreConfig; 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 #region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node) public override async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
{ {
var ret = new RetResult(); var ret = new RetResult();
try try
@ -53,7 +50,18 @@ public class CoreConfigSingboxService
await GenInbounds(singboxConfig); 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); 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(); var ret = new RetResult();
try try
@ -127,7 +135,7 @@ public class CoreConfigSingboxService
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (it.ConfigType == EConfigType.Custom) if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
{ {
continue; continue;
} }
@ -202,16 +210,29 @@ public class CoreConfigSingboxService
continue; continue;
} }
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); var server = await GenServer(item);
await GenOutbound(item, outbound); if (server is null)
outbound.tag = Global.ProxyTag + inbound.listen_port.ToString(); {
singboxConfig.outbounds.Add(outbound); 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 //rule
Rule4Sbox rule = new() Rule4Sbox rule = new()
{ {
inbound = new List<string> { inbound.tag }, inbound = new List<string> { inbound.tag },
outbound = outbound.tag outbound = tag
}; };
singboxConfig.route.rules.Add(rule); 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(); var ret = new RetResult();
try try
@ -275,7 +296,18 @@ public class CoreConfigSingboxService
} }
await GenLog(singboxConfig); 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 GenMoreOutbounds(node, singboxConfig);
await GenDnsDomains(null, singboxConfig, null); 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(); var ret = new RetResult();
try try
@ -339,7 +371,7 @@ public class CoreConfigSingboxService
var proxyProfiles = new List<ProfileItem>(); var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (it.ConfigType == EConfigType.Custom) if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
{ {
continue; 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(); var ret = new RetResult();
if (node == null || fileName is null) 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 #endregion public gen function
#region private gen function #region private gen function
@ -534,15 +652,6 @@ public class CoreConfigSingboxService
singboxConfig.inbounds.Add(inbound); singboxConfig.inbounds.Add(inbound);
inbound.listen_port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); 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) if (_config.Inbound.First().SecondLocalPortEnabled)
{ {
@ -587,8 +696,6 @@ public class CoreConfigSingboxService
tunInbound.mtu = _config.TunModeItem.Mtu; tunInbound.mtu = _config.TunModeItem.Mtu;
tunInbound.strict_route = _config.TunModeItem.StrictRoute; tunInbound.strict_route = _config.TunModeItem.StrictRoute;
tunInbound.stack = _config.TunModeItem.Stack; 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) if (_config.TunModeItem.EnableIPv6Address == false)
{ {
tunInbound.address = ["172.18.0.1/30"]; tunInbound.address = ["172.18.0.1/30"];
@ -621,6 +728,17 @@ public class CoreConfigSingboxService
outbound.server_port = node.Port; outbound.server_port = node.Port;
outbound.type = Global.ProtocolTypes[node.ConfigType]; 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) switch (node.ConfigType)
{ {
case EConfigType.VMess: case EConfigType.VMess:
@ -730,13 +848,9 @@ public class CoreConfigSingboxService
outbound.congestion_control = node.HeaderType; outbound.congestion_control = node.HeaderType;
break; break;
} }
case EConfigType.WireGuard: case EConfigType.Anytls:
{ {
outbound.private_key = node.Id; outbound.password = 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();
break; break;
} }
} }
@ -752,6 +866,76 @@ public class CoreConfigSingboxService
return 0; 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) private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
{ {
try try
@ -918,26 +1102,42 @@ public class CoreConfigSingboxService
} }
//current proxy //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); var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
//Previous proxy //Previous proxy
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
string? prevOutboundTag = null; string? prevOutboundTag = null;
if (prevNode is not 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}"; prevOutboundTag = $"prev-{Global.ProxyTag}";
prevOutbound.tag = prevOutboundTag; var prevServer = await GenServer(prevNode);
singboxConfig.outbounds.Add(prevOutbound); 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) catch (Exception ex)
@ -960,11 +1160,13 @@ public class CoreConfigSingboxService
} }
var resultOutbounds = new List<Outbound4Sbox>(); var resultOutbounds = new List<Outbound4Sbox>();
var resultEndpoints = new List<Endpoints4Sbox>(); // For endpoints
var prevOutbounds = new List<Outbound4Sbox>(); // Separate list for prev outbounds 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 var proxyTags = new List<string>(); // For selector and urltest outbounds
// Cache for chain proxies to avoid duplicate generation // 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 var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
int prevIndex = 0; // Index for prev outbounds int prevIndex = 0; // Index for prev outbounds
@ -976,19 +1178,18 @@ public class CoreConfigSingboxService
// Handle proxy chain // Handle proxy chain
string? prevTag = null; string? prevTag = null;
var currentOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); var currentServer = await GenServer(node);
var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null); var nextServer = nextProxyCache.GetValueOrDefault(node.Subid, null);
if (nextOutbound != null) if (nextServer != null)
{ {
nextOutbound = JsonUtils.DeepCopy(nextOutbound); nextServer = JsonUtils.DeepCopy(nextServer);
} }
var subItem = await AppHandler.Instance.GetSubItem(node.Subid); var subItem = await AppHandler.Instance.GetSubItem(node.Subid);
// current proxy // current proxy
await GenOutbound(node, currentOutbound); currentServer.tag = $"{Global.ProxyTag}-{index}";
currentOutbound.tag = $"{Global.ProxyTag}-{index}"; proxyTags.Add(currentServer.tag);
proxyTags.Add(currentOutbound.tag);
if (!node.Subid.IsNullOrEmpty()) if (!node.Subid.IsNullOrEmpty())
{ {
@ -1000,7 +1201,7 @@ public class CoreConfigSingboxService
{ {
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
if (prevNode is not null if (prevNode is not null
&& prevNode.ConfigType != EConfigType.Custom) && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType))
{ {
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(prevNode, prevOutbound); await GenOutbound(prevNode, prevOutbound);
@ -1011,18 +1212,32 @@ public class CoreConfigSingboxService
prevProxyTags[node.Subid] = prevTag; prevProxyTags[node.Subid] = prevTag;
} }
nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound); nextServer = await GenChainOutbounds(subItem, currentServer, prevTag, nextServer);
if (!nextProxyCache.ContainsKey(node.Subid)) 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) // Add urltest outbound (auto selection based on latency)
@ -1055,6 +1270,9 @@ public class CoreConfigSingboxService
resultOutbounds.AddRange(prevOutbounds); resultOutbounds.AddRange(prevOutbounds);
resultOutbounds.AddRange(singboxConfig.outbounds); resultOutbounds.AddRange(singboxConfig.outbounds);
singboxConfig.outbounds = resultOutbounds; singboxConfig.outbounds = resultOutbounds;
singboxConfig.endpoints ??= new List<Endpoints4Sbox>();
resultEndpoints.AddRange(singboxConfig.endpoints);
singboxConfig.endpoints = resultEndpoints;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1076,7 +1294,7 @@ public class CoreConfigSingboxService
/// <returns> /// <returns>
/// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists.
/// </returns> /// </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 try
{ {
@ -1090,13 +1308,9 @@ public class CoreConfigSingboxService
// Next proxy // Next proxy
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (nextNode is not null if (nextNode is not null
&& nextNode.ConfigType != EConfigType.Custom) && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType))
{ {
if (nextOutbound == null) nextOutbound ??= await GenServer(nextNode);
{
nextOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(nextNode, nextOutbound);
}
nextOutbound.tag = outbound.tag; nextOutbound.tag = outbound.tag;
outbound.tag = $"mid-{outbound.tag}"; outbound.tag = $"mid-{outbound.tag}";
@ -1115,7 +1329,7 @@ public class CoreConfigSingboxService
{ {
try try
{ {
var dnsOutbound = "dns_out"; singboxConfig.route.final = Global.ProxyTag;
if (_config.TunModeItem.EnableTun) if (_config.TunModeItem.EnableTun)
{ {
@ -1131,7 +1345,7 @@ public class CoreConfigSingboxService
singboxConfig.route.rules.Add(new() singboxConfig.route.rules.Add(new()
{ {
port = new() { 53 }, port = new() { 53 },
outbound = dnsOutbound, action = "hijack-dns",
process_name = lstDnsExe 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() singboxConfig.route.rules.Add(new()
{ {
port = [53], action = "sniff"
network = ["udp"], });
outbound = dnsOutbound 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() 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 routing = await ConfigHandler.GetDefaultRouting(_config);
var ipRules = new List<RulesItem>();
if (routing != null) if (routing != null)
{ {
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet); var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
@ -1172,9 +1415,21 @@ public class CoreConfigSingboxService
if (item.Enabled) if (item.Enabled)
{ {
await GenRoutingUserRule(item, singboxConfig); 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) catch (Exception ex)
{ {
@ -1222,10 +1477,15 @@ public class CoreConfigSingboxService
item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig); item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig);
var rules = singboxConfig.route.rules; 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()) if (item.Port.IsNotEmpty())
{ {
@ -1349,24 +1609,28 @@ public class CoreConfigSingboxService
{ {
return false; return false;
} }
else if (address.StartsWith("geoip:!"))
{
return false;
}
else if (address.Equals("geoip:private")) else if (address.Equals("geoip:private"))
{ {
rule.ip_is_private = true; rule.ip_is_private = true;
} }
else if (address.StartsWith("geoip:")) else if (address.StartsWith("geoip:"))
{ {
if (rule.geoip is null) rule.geoip ??= new();
{ rule.geoip = new(); }
rule.geoip?.Add(address.Substring(6)); 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 else
{ {
if (rule.ip_cidr is null) rule.ip_cidr ??= new();
{ rule.ip_cidr = new(); }
rule.ip_cidr?.Add(address); rule.ip_cidr?.Add(address);
} }
return true; return true;
@ -1381,18 +1645,29 @@ public class CoreConfigSingboxService
var node = await AppHandler.Instance.GetProfileItemViaRemarks(outboundTag); var node = await AppHandler.Instance.GetProfileItemViaRemarks(outboundTag);
if (node == null if (node == null
|| node.ConfigType == EConfigType.Custom) || !Global.SingboxSupportConfigType.Contains(node.ConfigType))
{ {
return Global.ProxyTag; return Global.ProxyTag;
} }
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); var server = await GenServer(node);
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); if (server is null)
await GenOutbound(node, outbound); {
outbound.tag = Global.ProxyTag + node.IndexId.ToString(); return Global.ProxyTag;
singboxConfig.outbounds.Add(outbound); }
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) private async Task<int> GenDns(ProfileItem? node, SingboxConfig singboxConfig)
@ -1417,7 +1692,14 @@ public class CoreConfigSingboxService
} }
singboxConfig.dns = dns4Sbox; 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) catch (Exception ex)
{ {
@ -1432,6 +1714,75 @@ public class CoreConfigSingboxService
dns4Sbox.servers ??= []; dns4Sbox.servers ??= [];
dns4Sbox.rules ??= []; 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"; var tag = "local_local";
dns4Sbox.servers.Add(new() dns4Sbox.servers.Add(new()
{ {
@ -1479,6 +1830,91 @@ public class CoreConfigSingboxService
return await Task.FromResult(0); 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) private async Task<int> GenExperimental(SingboxConfig singboxConfig)
{ {
//if (_config.guiItem.enableStatistics) //if (_config.guiItem.enableStatistics)

View file

@ -1,22 +1,15 @@
using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
namespace ServiceLib.Services.CoreConfig; 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 #region public gen function
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node) public override async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
{ {
var ret = new RetResult(); var ret = new RetResult();
try 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(); var ret = new RetResult();
@ -116,11 +109,7 @@ public class CoreConfigV2rayService
var proxyProfiles = new List<ProfileItem>(); var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (it.ConfigType == EConfigType.Custom) if (!Global.XraySupportConfigType.Contains(it.ConfigType))
{
continue;
}
if (it.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC)
{ {
continue; 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(); var ret = new RetResult();
try try
@ -255,7 +244,7 @@ public class CoreConfigV2rayService
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (it.ConfigType == EConfigType.Custom) if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
{ {
continue; 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(); var ret = new RetResult();
try 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 #endregion public gen function
#region private gen function #region private gen function
@ -638,9 +701,7 @@ public class CoreConfigV2rayService
var node = await AppHandler.Instance.GetProfileItemViaRemarks(outboundTag); var node = await AppHandler.Instance.GetProfileItemViaRemarks(outboundTag);
if (node == null if (node == null
|| node.ConfigType == EConfigType.Custom || !Global.SingboxSupportConfigType.Contains(node.ConfigType))
|| node.ConfigType == EConfigType.Hysteria2
|| node.ConfigType == EConfigType.TUIC)
{ {
return Global.ProxyTag; return Global.ProxyTag;
} }
@ -1219,9 +1280,7 @@ public class CoreConfigV2rayService
// Previous proxy // Previous proxy
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
if (prevNode is not null if (prevNode is not null
&& prevNode.ConfigType != EConfigType.Custom && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)
&& prevNode.ConfigType != EConfigType.Hysteria2
&& prevNode.ConfigType != EConfigType.TUIC
&& Utils.IsDomain(prevNode.Address)) && Utils.IsDomain(prevNode.Address))
{ {
domainList.Add(prevNode.Address); domainList.Add(prevNode.Address);
@ -1230,9 +1289,7 @@ public class CoreConfigV2rayService
// Next proxy // Next proxy
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (nextNode is not null if (nextNode is not null
&& nextNode.ConfigType != EConfigType.Custom && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)
&& nextNode.ConfigType != EConfigType.Hysteria2
&& nextNode.ConfigType != EConfigType.TUIC
&& Utils.IsDomain(nextNode.Address)) && Utils.IsDomain(nextNode.Address))
{ {
domainList.Add(nextNode.Address); domainList.Add(nextNode.Address);
@ -1348,9 +1405,7 @@ public class CoreConfigV2rayService
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
string? prevOutboundTag = null; string? prevOutboundTag = null;
if (prevNode is not null if (prevNode is not null
&& prevNode.ConfigType != EConfigType.Custom && Global.XraySupportConfigType.Contains(prevNode.ConfigType))
&& prevNode.ConfigType != EConfigType.Hysteria2
&& prevNode.ConfigType != EConfigType.TUIC)
{ {
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound); var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(prevNode, prevOutbound); await GenOutbound(prevNode, prevOutbound);
@ -1423,9 +1478,7 @@ public class CoreConfigV2rayService
{ {
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
if (prevNode is not null if (prevNode is not null
&& prevNode.ConfigType != EConfigType.Custom && !Global.XraySupportConfigType.Contains(prevNode.ConfigType))
&& prevNode.ConfigType != EConfigType.Hysteria2
&& prevNode.ConfigType != EConfigType.TUIC)
{ {
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound); var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(prevNode, prevOutbound); await GenOutbound(prevNode, prevOutbound);
@ -1492,9 +1545,7 @@ public class CoreConfigV2rayService
// Next proxy // Next proxy
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
if (nextNode is not null if (nextNode is not null
&& nextNode.ConfigType != EConfigType.Custom && !Global.XraySupportConfigType.Contains(nextNode.ConfigType))
&& nextNode.ConfigType != EConfigType.Hysteria2
&& nextNode.ConfigType != EConfigType.TUIC)
{ {
if (nextOutbound == null) 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> /// <summary>
/// Core configuration file processing class /// Core configuration file processing class
/// </summary> /// </summary>
public class CoreConfigClashService public class CoreConfigClashService(Config config) : CoreConfigServiceMinimalBase(config)
{ {
private Config _config; protected override async Task<RetResult> GeneratePassthroughConfig(ProfileItem node, int port)
private static readonly string _tag = "CoreConfigClashService";
public CoreConfigClashService(Config config)
{ {
_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(); var ret = new RetResult();
if (node == null || fileName is null) 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.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
@ -70,7 +71,7 @@ public class SpeedtestService
var lstSelected = new List<ServerTestItem>(); var lstSelected = new List<ServerTestItem>();
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (it.ConfigType == EConfigType.Custom) if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType)))
{ {
continue; continue;
} }
@ -122,7 +123,7 @@ public class SpeedtestService
List<Task> tasks = []; List<Task> tasks = [];
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (it.ConfigType == EConfigType.Custom) if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType)))
{ {
continue; continue;
} }
@ -207,7 +208,7 @@ public class SpeedtestService
{ {
continue; continue;
} }
if (it.ConfigType == EConfigType.Custom) if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType)))
{ {
continue; continue;
} }
@ -244,7 +245,7 @@ public class SpeedtestService
UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip); UpdateFunc(it.IndexId, "", ResUI.SpeedtestingSkip);
continue; continue;
} }
if (it.ConfigType == EConfigType.Custom) if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || Global.SingboxSupportConfigType.Contains(it.ConfigType)))
{ {
continue; continue;
} }
@ -358,8 +359,8 @@ public class SpeedtestService
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize) private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
{ {
List<List<ServerTestItem>> lstTest = new(); List<List<ServerTestItem>> lstTest = new();
var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC)).ToList(); var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC).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++) for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++)
{ {

View file

@ -20,10 +20,15 @@ public class MainWindowViewModel : MyReactiveObject
public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; } public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; } public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddWireguardServerCmd { get; } public ReactiveCommand<Unit, Unit> AddWireguardServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddAnytlsServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; } public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; } public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; } public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaImageCmd { 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 //Subscription
public ReactiveCommand<Unit, Unit> SubSettingCmd { get; } public ReactiveCommand<Unit, Unit> SubSettingCmd { get; }
@ -111,6 +116,26 @@ public class MainWindowViewModel : MyReactiveObject
{ {
await AddServerAsync(true, EConfigType.WireGuard); 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 () => AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(true, EConfigType.Custom); await AddServerAsync(true, EConfigType.Custom);

View file

@ -1,6 +1,7 @@
using System.Reactive; using System.Reactive;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
using ServiceLib.Models;
namespace ServiceLib.ViewModels; namespace ServiceLib.ViewModels;
@ -104,6 +105,21 @@ public class OptionSettingViewModel : MyReactiveObject
#endregion CoreType #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 ReactiveCommand<Unit, Unit> SaveCmd { get; }
public OptionSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView) public OptionSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
@ -210,14 +226,13 @@ public class OptionSettingViewModel : MyReactiveObject
#endregion Tun mode #endregion Tun mode
await InitCoreType(); await InitCoreType();
await InitSplitCoreItem();
} }
private async Task InitCoreType() 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))) foreach (EConfigType it in Enum.GetValues(typeof(EConfigType)))
{ {
@ -232,6 +247,7 @@ public class OptionSettingViewModel : MyReactiveObject
CoreType = ECoreType.Xray CoreType = ECoreType.Xray
}); });
} }
_config.CoreTypeItem.ForEach(it => _config.CoreTypeItem.ForEach(it =>
{ {
var type = it.CoreType.ToString(); var type = it.CoreType.ToString();
@ -269,6 +285,87 @@ public class OptionSettingViewModel : MyReactiveObject
await Task.CompletedTask; 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() private async Task SaveSettingAsync()
{ {
if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString()) if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString())
@ -362,6 +459,7 @@ public class OptionSettingViewModel : MyReactiveObject
//coreType //coreType
await SaveCoreType(); await SaveCoreType();
await SaveSplitCoreType();
if (await ConfigHandler.SaveConfig(_config) == 0) if (await ConfigHandler.SaveConfig(_config) == 0)
{ {
@ -420,4 +518,46 @@ public class OptionSettingViewModel : MyReactiveObject
} }
await Task.CompletedTask; 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) 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) if (result.Success != true)
{ {
NoticeHandler.Instance.Enqueue(result.Msg); NoticeHandler.Instance.Enqueue(result.Msg);
@ -793,7 +794,8 @@ public class ProfilesViewModel : MyReactiveObject
{ {
return; 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) if (result.Success != true)
{ {
NoticeHandler.Instance.Enqueue(result.Msg); NoticeHandler.Instance.Enqueue(result.Msg);

View file

@ -533,6 +533,171 @@
HorizontalAlignment="Left" HorizontalAlignment="Left"
Watermark="1500" /> Watermark="1500" />
</Grid> </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 <Separator
x:Name="sepa2" x:Name="sepa2"

View file

@ -78,7 +78,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridHysteria2.IsVisible = true; gridHysteria2.IsVisible = true;
sepa2.IsVisible = false; sepa2.IsVisible = false;
gridTransport.IsVisible = false; gridTransport.IsVisible = false;
cmbCoreType.IsEnabled = false; cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes.AppendEmpty();
cmbFingerprint.IsEnabled = false; cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty; cmbFingerprint.SelectedValue = string.Empty;
break; break;
@ -87,11 +87,11 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridTuic.IsVisible = true; gridTuic.IsVisible = true;
sepa2.IsVisible = false; sepa2.IsVisible = false;
gridTransport.IsVisible = false; gridTransport.IsVisible = false;
cmbCoreType.IsEnabled = false; cmbCoreType.ItemsSource = Global.TuicCoreTypes.AppendEmpty();
cmbFingerprint.IsEnabled = false; cmbFingerprint.IsEnabled = false;
cmbFingerprint.SelectedValue = string.Empty; cmbFingerprint.SelectedValue = string.Empty;
cmbHeaderType8.ItemsSource = Global.TuicCongestionControls; cmbHeaderType8.ItemsSource = Global.CongestionControls;
break; break;
case EConfigType.WireGuard: case EConfigType.WireGuard:
@ -102,6 +102,55 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
gridTls.IsVisible = false; gridTls.IsVisible = false;
break; 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; 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.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables);
break; 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.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.SelectedValue).DisposeWith(disposables);

View file

@ -46,6 +46,12 @@
<Separator /> <Separator />
<MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" /> <MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
<MenuItem x:Name="menuAddTuicServer" Header="{x:Static resx:ResUI.menuAddTuicServer}" /> <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>
<MenuItem Padding="8,0"> <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.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).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.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.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);

View file

@ -802,101 +802,298 @@
</TabItem> </TabItem>
<TabItem Header="{x:Static resx:ResUI.TbSettingsCoreType}"> <TabItem Header="{x:Static resx:ResUI.TbSettingsCoreType}">
<Grid <ScrollViewer VerticalScrollBarVisibility="Visible">
Margin="{StaticResource Margin4}" <Grid Margin="{StaticResource Margin4}">
ColumnDefinitions="Auto,Auto" <Grid.RowDefinitions>
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> <RowDefinition Height="Auto" />
<TextBlock </Grid.RowDefinitions>
Grid.Row="1" <Grid.ColumnDefinitions>
Grid.Column="0" <ColumnDefinition Width="Auto" />
Margin="{StaticResource Margin4}" <ColumnDefinition Width="Auto" />
VerticalAlignment="Center" </Grid.ColumnDefinitions>
Text="VMess" />
<ComboBox
x:Name="cmbCoreType1"
Grid.Row="1"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock <Grid
Grid.Row="2" Grid.Row="0"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}">
VerticalAlignment="Center" <Grid.RowDefinitions>
Text="Custom" /> <RowDefinition Height="Auto" />
<ComboBox <RowDefinition Height="Auto" />
x:Name="cmbCoreType2" <RowDefinition Height="Auto" />
Grid.Row="2" <RowDefinition Height="Auto" />
Grid.Column="1" <RowDefinition Height="Auto" />
Width="200" <RowDefinition Height="Auto" />
Margin="{StaticResource Margin4}" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock <TextBlock
Grid.Row="3" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Shadowsocks" /> Text="VMess" />
<ComboBox <ComboBox
x:Name="cmbCoreType3" x:Name="cmbCoreType1"
Grid.Row="3" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock <TextBlock
Grid.Row="4" Grid.Row="2"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Socks" /> Text="Custom" />
<ComboBox <ComboBox
x:Name="cmbCoreType4" x:Name="cmbCoreType2"
Grid.Row="4" Grid.Row="2"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock <TextBlock
Grid.Row="5" Grid.Row="3"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="VLESS" /> Text="Shadowsocks" />
<ComboBox <ComboBox
x:Name="cmbCoreType5" x:Name="cmbCoreType3"
Grid.Row="5" Grid.Row="3"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock <TextBlock
Grid.Row="6" Grid.Row="4"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Trojan" /> Text="Socks" />
<ComboBox <ComboBox
x:Name="cmbCoreType6" x:Name="cmbCoreType4"
Grid.Row="6" Grid.Row="4"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock <TextBlock
Grid.Row="7" Grid.Row="5"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="Wireguard" /> Text="VLESS" />
<ComboBox <ComboBox
x:Name="cmbCoreType9" x:Name="cmbCoreType5"
Grid.Row="7" Grid.Row="5"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
</Grid>
<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> </TabItem>
</TabControl> </TabControl>
</DockPanel> </DockPanel>

View file

@ -41,6 +41,17 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
cmbCoreType6.ItemsSource = Global.CoreTypes; cmbCoreType6.ItemsSource = Global.CoreTypes;
cmbCoreType9.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(); cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(2, 7).ToList();
cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).ToList(); cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).ToList();
cmbSpeedTestUrl.ItemsSource = Global.SpeedTestUrls; 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.CoreType6, v => v.cmbCoreType6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.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); 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) 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) private void btnCancel_Click(object? sender, RoutedEventArgs e)

View file

@ -707,6 +707,228 @@
materialDesign:HintAssist.Hint="1500" materialDesign:HintAssist.Hint="1500"
Style="{StaticResource DefTextBox}" /> Style="{StaticResource DefTextBox}" />
</Grid> </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 <Separator
x:Name="sepa2" x:Name="sepa2"

View file

@ -72,7 +72,7 @@ public partial class AddServerWindow
gridHysteria2.Visibility = Visibility.Visible; gridHysteria2.Visibility = Visibility.Visible;
sepa2.Visibility = Visibility.Collapsed; sepa2.Visibility = Visibility.Collapsed;
gridTransport.Visibility = Visibility.Collapsed; gridTransport.Visibility = Visibility.Collapsed;
cmbCoreType.IsEnabled = false; cmbCoreType.ItemsSource = Global.Hysteria2CoreTypes.AppendEmpty();
cmbFingerprint.IsEnabled = false; cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty; cmbFingerprint.Text = string.Empty;
break; break;
@ -81,11 +81,11 @@ public partial class AddServerWindow
gridTuic.Visibility = Visibility.Visible; gridTuic.Visibility = Visibility.Visible;
sepa2.Visibility = Visibility.Collapsed; sepa2.Visibility = Visibility.Collapsed;
gridTransport.Visibility = Visibility.Collapsed; gridTransport.Visibility = Visibility.Collapsed;
cmbCoreType.IsEnabled = false; cmbCoreType.ItemsSource = Global.TuicCoreTypes.AppendEmpty();
cmbFingerprint.IsEnabled = false; cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty; cmbFingerprint.Text = string.Empty;
cmbHeaderType8.ItemsSource = Global.TuicCongestionControls; cmbHeaderType8.ItemsSource = Global.CongestionControls;
break; break;
case EConfigType.WireGuard: case EConfigType.WireGuard:
@ -96,6 +96,55 @@ public partial class AddServerWindow
gridTls.Visibility = Visibility.Collapsed; gridTls.Visibility = Visibility.Collapsed;
break; 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; 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.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables);
break; 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.Network, v => v.cmbNetwork.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType.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" x:Name="menuAddTuicServer"
Height="{StaticResource MenuItemHeight}" Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddTuicServer}" /> 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> </MenuItem>
</Menu> </Menu>
<Separator /> <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.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).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.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.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);

View file

@ -1107,126 +1107,329 @@
</TabItem> </TabItem>
<TabItem Header="{x:Static resx:ResUI.TbSettingsCoreType}"> <TabItem Header="{x:Static resx:ResUI.TbSettingsCoreType}">
<Grid Margin="{StaticResource Margin8}"> <ScrollViewer VerticalScrollBarVisibility="Visible">
<Grid.RowDefinitions> <Grid Margin="{StaticResource Margin8}">
<RowDefinition Height="Auto" /> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> </Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <Grid.ColumnDefinitions>
<RowDefinition Height="Auto" /> <ColumnDefinition Width="Auto" />
<RowDefinition Height="Auto" /> <ColumnDefinition Width="Auto" />
<RowDefinition Height="Auto" /> </Grid.ColumnDefinitions>
<RowDefinition Height="Auto" /> <Grid
</Grid.RowDefinitions> Grid.Row="0"
<Grid.ColumnDefinitions> Grid.Column="0"
<ColumnDefinition Width="Auto" /> Margin="{StaticResource Margin8}">
<ColumnDefinition Width="Auto" /> <Grid.RowDefinitions>
</Grid.ColumnDefinitions> <RowDefinition Height="Auto" />
<TextBlock <RowDefinition Height="Auto" />
Grid.Row="1" <RowDefinition Height="Auto" />
Grid.Column="0" <RowDefinition Height="Auto" />
Margin="{StaticResource Margin8}" <RowDefinition Height="Auto" />
VerticalAlignment="Center" <RowDefinition Height="Auto" />
Style="{StaticResource ToolbarTextBlock}" <RowDefinition Height="Auto" />
Text="VMess" /> <RowDefinition Height="Auto" />
<ComboBox </Grid.RowDefinitions>
x:Name="cmbCoreType1" <Grid.ColumnDefinitions>
Grid.Row="1" <ColumnDefinition Width="Auto" />
Grid.Column="1" <ColumnDefinition Width="Auto" />
Width="200" </Grid.ColumnDefinitions>
Margin="{StaticResource Margin8}" <TextBlock
Style="{StaticResource DefComboBox}" /> 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 <TextBlock
Grid.Row="2" Grid.Row="2"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="Custom" /> Text="Custom" />
<ComboBox <ComboBox
x:Name="cmbCoreType2" x:Name="cmbCoreType2"
Grid.Row="2" Grid.Row="2"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="3" Grid.Row="3"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="Shadowsocks" /> Text="Shadowsocks" />
<ComboBox <ComboBox
x:Name="cmbCoreType3" x:Name="cmbCoreType3"
Grid.Row="3" Grid.Row="3"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="4" Grid.Row="4"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="Socks" /> Text="Socks" />
<ComboBox <ComboBox
x:Name="cmbCoreType4" x:Name="cmbCoreType4"
Grid.Row="4" Grid.Row="4"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="5" Grid.Row="5"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="VLESS" /> Text="VLESS" />
<ComboBox <ComboBox
x:Name="cmbCoreType5" x:Name="cmbCoreType5"
Grid.Row="5" Grid.Row="5"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="6" Grid.Row="6"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="Trojan" /> Text="Trojan" />
<ComboBox <ComboBox
x:Name="cmbCoreType6" x:Name="cmbCoreType6"
Grid.Row="6" Grid.Row="6"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="7" Grid.Row="7"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="Wireguard" /> Text="Wireguard" />
<ComboBox <ComboBox
x:Name="cmbCoreType9" x:Name="cmbCoreType9"
Grid.Row="7" Grid.Row="7"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
</Grid> </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> </TabItem>
</TabControl> </TabControl>
</DockPanel> </DockPanel>

View file

@ -43,6 +43,17 @@ public partial class OptionSettingWindow
cmbCoreType6.ItemsSource = Global.CoreTypes; cmbCoreType6.ItemsSource = Global.CoreTypes;
cmbCoreType9.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(); cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(2, 7).ToList();
cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).ToList(); cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).ToList();
cmbSpeedTestUrl.ItemsSource = Global.SpeedTestUrls; 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.CoreType6, v => v.cmbCoreType6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.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); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
}); });
WindowsUtils.SetDarkBorder(this, AppHandler.Instance.Config.UiItem.CurrentTheme); 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) 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) private void btnCancel_Click(object sender, System.Windows.RoutedEventArgs e)