diff --git a/v2rayN/ServiceLib/Common/Utils.cs b/v2rayN/ServiceLib/Common/Utils.cs index 63540ee2..369cc0f4 100644 --- a/v2rayN/ServiceLib/Common/Utils.cs +++ b/v2rayN/ServiceLib/Common/Utils.cs @@ -466,11 +466,11 @@ public class Utils return false; } - public static int GetFreePort(int defaultPort = 9090) + public static int GetFreePort(int defaultPort = 0) { try { - if (!Utils.PortInUse(defaultPort)) + if (!(defaultPort == 0 || Utils.PortInUse(defaultPort))) { return defaultPort; } diff --git a/v2rayN/ServiceLib/Enums/EConfigType.cs b/v2rayN/ServiceLib/Enums/EConfigType.cs index f56d0e0f..6698f962 100644 --- a/v2rayN/ServiceLib/Enums/EConfigType.cs +++ b/v2rayN/ServiceLib/Enums/EConfigType.cs @@ -11,5 +11,6 @@ public enum EConfigType Hysteria2 = 7, TUIC = 8, WireGuard = 9, - HTTP = 10 + HTTP = 10, + Anytls = 11 } diff --git a/v2rayN/ServiceLib/Enums/EViewAction.cs b/v2rayN/ServiceLib/Enums/EViewAction.cs index a72e8765..4f84a7cf 100644 --- a/v2rayN/ServiceLib/Enums/EViewAction.cs +++ b/v2rayN/ServiceLib/Enums/EViewAction.cs @@ -29,6 +29,7 @@ public enum EViewAction DNSSettingWindow, RoutingSettingWindow, OptionSettingWindow, + CustomConfigWindow, GlobalHotkeySettingWindow, SubSettingWindow, DispatcherSpeedTest, diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 0728b2ea..4888ab6a 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -169,7 +169,8 @@ public class Global { EConfigType.Trojan, "trojan://" }, { EConfigType.Hysteria2, "hysteria2://" }, { EConfigType.TUIC, "tuic://" }, - { EConfigType.WireGuard, "wireguard://" } + { EConfigType.WireGuard, "wireguard://" }, + { EConfigType.Anytls, "anytls://" } }; public static readonly Dictionary ProtocolTypes = new() @@ -182,7 +183,8 @@ public class Global { EConfigType.Trojan, "trojan" }, { EConfigType.Hysteria2, "hysteria2" }, { EConfigType.TUIC, "tuic" }, - { EConfigType.WireGuard, "wireguard" } + { EConfigType.WireGuard, "wireguard" }, + { EConfigType.Anytls, "anytls" } }; public static readonly List VmessSecurities = diff --git a/v2rayN/ServiceLib/Handler/AppHandler.cs b/v2rayN/ServiceLib/Handler/AppHandler.cs index ad9a4029..d55245d4 100644 --- a/v2rayN/ServiceLib/Handler/AppHandler.cs +++ b/v2rayN/ServiceLib/Handler/AppHandler.cs @@ -64,6 +64,7 @@ public sealed class AppHandler SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); SQLiteHelper.Instance.CreateTable(); + SQLiteHelper.Instance.CreateTable(); return true; } @@ -203,6 +204,16 @@ public sealed class AppHandler return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.CoreType == eCoreType); } + public async Task?> CustomConfigItem() + { + return await SQLiteHelper.Instance.TableAsync().ToListAsync(); + } + + public async Task GetCustomConfigItem(ECoreType eCoreType) + { + return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.CoreType == eCoreType); + } + #endregion SqliteHelper #region Core Type diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 0eeadc46..e9272559 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -262,6 +262,7 @@ public class ConfigHandler EConfigType.Hysteria2 => await AddHysteria2Server(config, item), EConfigType.TUIC => await AddTuicServer(config, item), EConfigType.WireGuard => await AddWireguardServer(config, item), + EConfigType.Anytls => await AddAnytlsServer(config, item), _ => -1, }; return ret; @@ -786,6 +787,35 @@ public class ConfigHandler return 0; } + /// + /// Add or edit a Anytls server + /// Validates and processes Anytls-specific settings + /// + /// Current configuration + /// Anytls profile to add + /// Whether to save to file + /// 0 if successful, -1 if failed + public static async Task 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; + } + /// /// Sort the server list by the specified column /// Updates the sort order in the profile extension data @@ -1295,6 +1325,7 @@ public class ConfigHandler EConfigType.Hysteria2 => await AddHysteria2Server(config, profileItem, false), EConfigType.TUIC => await AddTuicServer(config, profileItem, false), EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false), + EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false), _ => -1, }; @@ -2153,6 +2184,54 @@ public class ConfigHandler #endregion DNS + #region Custom Config + + public static async Task InitBuiltinCustomConfig(Config config) + { + var items = await AppHandler.Instance.CustomConfigItem(); + if (items.Count <= 0) + { + var item = new CustomConfigItem() + { + Remarks = "V2ray", + CoreType = ECoreType.Xray, + }; + await SaveCustomConfigItem(config, item); + + var item2 = new CustomConfigItem() + { + Remarks = "sing-box", + CoreType = ECoreType.sing_box, + }; + await SaveCustomConfigItem(config, item2); + } + + return 0; + } + public static async Task SaveCustomConfigItem(Config config, CustomConfigItem item) + { + if (item == null) + { + return -1; + } + + if (item.Id.IsNullOrEmpty()) + { + item.Id = Utils.GetGuid(false); + } + + if (await SQLiteHelper.Instance.ReplaceAsync(item) > 0) + { + return 0; + } + else + { + return -1; + } + } + + #endregion Custom Config + #region Regional Presets /// diff --git a/v2rayN/ServiceLib/Handler/CoreHandler.cs b/v2rayN/ServiceLib/Handler/CoreHandler.cs index a0e20d07..f7ad2285 100644 --- a/v2rayN/ServiceLib/Handler/CoreHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreHandler.cs @@ -25,8 +25,6 @@ public class CoreHandler Environment.SetEnvironmentVariable(Global.V2RayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable(Global.XrayLocalAsset, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable(Global.XrayLocalCert, Utils.GetBinPath(""), EnvironmentVariableTarget.Process); - // TODO Temporary addition to support proper use of sing-box v1.12 - Environment.SetEnvironmentVariable("ENABLE_DEPRECATED_SPECIAL_OUTBOUNDS", "true", EnvironmentVariableTarget.Process); //Copy the bin folder to the storage location (for init) if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1") @@ -103,7 +101,7 @@ public class CoreHandler public async Task LoadCoreConfigSpeedtest(List selecteds) { - var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC) ? ECoreType.sing_box : ECoreType.Xray; + var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) ? ECoreType.sing_box : ECoreType.Xray; var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); var configPath = Utils.GetBinConfigPath(fileName); var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType); diff --git a/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs new file mode 100644 index 00000000..3f1d40dc --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Fmt/AnytlsFmt.cs @@ -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(); + _ = GetStdTransport(item, Global.None, ref dicQuery); + + return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark); + } +} diff --git a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs index 3e8ab2ae..814d753d 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/FmtHandler.cs @@ -18,6 +18,7 @@ public class FmtHandler EConfigType.Hysteria2 => Hysteria2Fmt.ToUri(item), EConfigType.TUIC => TuicFmt.ToUri(item), EConfigType.WireGuard => WireguardFmt.ToUri(item), + EConfigType.Anytls => AnytlsFmt.ToUri(item), _ => null, }; @@ -75,6 +76,10 @@ public class FmtHandler { return WireguardFmt.Resolve(str, out msg); } + else if (str.StartsWith(Global.ProtocolShares[EConfigType.Anytls])) + { + return AnytlsFmt.Resolve(str, out msg); + } else { msg = ResUI.NonvmessOrssProtocol; diff --git a/v2rayN/ServiceLib/Models/CustomConfigItem.cs b/v2rayN/ServiceLib/Models/CustomConfigItem.cs new file mode 100644 index 00000000..3d4b6453 --- /dev/null +++ b/v2rayN/ServiceLib/Models/CustomConfigItem.cs @@ -0,0 +1,18 @@ +using SQLite; + +namespace ServiceLib.Models; + +[Serializable] +public class CustomConfigItem +{ + [PrimaryKey] + public string Id { get; set; } + + public string Remarks { get; set; } + public bool Enabled { get; set; } = false; + public ECoreType CoreType { get; set; } + public string? Config { get; set; } + public string? TunConfig { get; set; } + public bool? AddProxyOnly { get; set; } = false; + public string? ProxyDetour { get; set; } +} diff --git a/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayN/ServiceLib/Models/SingboxConfig.cs index 9ea1157d..c63acc4a 100644 --- a/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace ServiceLib.Models; public class SingboxConfig @@ -6,6 +8,7 @@ public class SingboxConfig public Dns4Sbox? dns { get; set; } public List inbounds { get; set; } public List outbounds { get; set; } + public List? endpoints { get; set; } public Route4Sbox route { get; set; } public Experimental4Sbox? experimental { get; set; } } @@ -29,7 +32,6 @@ public class Dns4Sbox public bool? independent_cache { get; set; } public bool? reverse_mapping { get; set; } public string? client_subnet { get; set; } - public Fakeip4Sbox? fakeip { get; set; } } public class Route4Sbox @@ -37,6 +39,7 @@ public class Route4Sbox public bool? auto_detect_interface { get; set; } public List rules { get; set; } public List? rule_set { get; set; } + public string? final { get; set; } } [Serializable] @@ -49,6 +52,7 @@ public class Rule4Sbox public string? mode { get; set; } public bool? ip_is_private { get; set; } public string? client_subnet { get; set; } + public int? rewrite_ttl { get; set; } public bool? invert { get; set; } public string? clash_mode { get; set; } public List? inbound { get; set; } @@ -67,6 +71,27 @@ public class Rule4Sbox public List? process_name { get; set; } public List? rule_set { get; set; } public List? rules { get; set; } + public string? action { get; set; } + public string? strategy { get; set; } + public List? sniffer { get; set; } + public string? rcode { get; set; } + public List? query_type { get; set; } + public List? answer { get; set; } + public List? ns { get; set; } + public List? 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? source_port_range { get; set; } + public List? network_type { get; set; } + public bool? network_is_expensive { get; set; } + public bool? network_is_constrained { get; set; } + public List? wifi_ssid { get; set; } + public List? wifi_bssid { get; set; } + public bool? rule_set_ip_cidr_match_source { get; set; } + public bool? rule_set_ip_cidr_accept_empty { get; set; } } [Serializable] @@ -76,7 +101,6 @@ public class Inbound4Sbox public string tag { get; set; } public string listen { get; set; } public int? listen_port { get; set; } - public string? domain_strategy { get; set; } public string interface_name { get; set; } public List? address { get; set; } public int? mtu { get; set; } @@ -84,8 +108,6 @@ public class Inbound4Sbox public bool? strict_route { get; set; } public bool? endpoint_independent_nat { get; set; } public string? stack { get; set; } - public bool? sniff { get; set; } - public bool? sniff_override_destination { get; set; } public List users { get; set; } } @@ -95,10 +117,8 @@ public class User4Sbox public string password { get; set; } } -public class Outbound4Sbox +public class Outbound4Sbox : BaseServer4Sbox { - public string type { get; set; } - public string tag { get; set; } public string? server { get; set; } public int? server_port { get; set; } public List? server_ports { get; set; } @@ -113,7 +133,6 @@ public class Outbound4Sbox public int? recv_window_conn { get; set; } public int? recv_window { get; set; } public bool? disable_mtu_discovery { get; set; } - public string? detour { get; set; } public string? method { get; set; } public string? username { get; set; } public string? password { get; set; } @@ -121,21 +140,36 @@ public class Outbound4Sbox public string? version { get; set; } public string? network { get; set; } public string? packet_encoding { get; set; } - public List? local_address { get; set; } - public string? private_key { get; set; } - public string? peer_public_key { get; set; } - public List? reserved { get; set; } - public int? mtu { get; set; } public string? plugin { get; set; } public string? plugin_opts { get; set; } - public Tls4Sbox? tls { get; set; } - public Multiplex4Sbox? multiplex { get; set; } - public Transport4Sbox? transport { get; set; } - public HyObfs4Sbox? obfs { get; set; } public List? outbounds { get; set; } public bool? interrupt_exist_connections { get; set; } } +public class Endpoints4Sbox : BaseServer4Sbox +{ + public bool? system { get; set; } + public string? name { get; set; } + public int? mtu { get; set; } + public List 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 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 allowed_ips { get; set; } + public int? persistent_keepalive_interval { get; set; } + public List reserved { get; set; } +} + public class Tls4Sbox { public bool enabled { get; set; } @@ -191,15 +225,25 @@ public class HyObfs4Sbox public string? password { get; set; } } -public class Server4Sbox +public class Server4Sbox : BaseServer4Sbox { - public string? tag { get; set; } + public string? inet4_range { get; set; } + public string? inet6_range { get; set; } + public string? client_subnet { get; set; } + public string? server { get; set; } + public new string? domain_resolver { get; set; } + [JsonPropertyName("interface")] public string? Interface { get; set; } + public int? server_port { get; set; } + public string? path { get; set; } + public Headers4Sbox? headers { get; set; } + // public List? path { get; set; } // hosts + public Dictionary? predefined { get; set; } + // Deprecated public string? address { get; set; } public string? address_resolver { get; set; } public string? address_strategy { get; set; } public string? strategy { get; set; } - public string? detour { get; set; } - public string? client_subnet { get; set; } + // Deprecated End } public class Experimental4Sbox @@ -229,13 +273,6 @@ public class Stats4Sbox public List? users { get; set; } } -public class Fakeip4Sbox -{ - public bool enabled { get; set; } - public string inet4_range { get; set; } - public string inet6_range { get; set; } -} - public class CacheFile4Sbox { public bool enabled { get; set; } @@ -254,3 +291,33 @@ public class Ruleset4Sbox public string? download_detour { get; set; } public string? update_interval { get; set; } } + +public abstract class DialFields4Sbox +{ + public string? detour { get; set; } + public string? bind_interface { get; set; } + public string? inet4_bind_address { get; set; } + public string? inet6_bind_address { get; set; } + public int? routing_mark { get; set; } + public bool? reuse_addr { get; set; } + public string? netns { get; set; } + public string? connect_timeout { get; set; } + public bool? tcp_fast_open { get; set; } + public bool? tcp_multi_path { get; set; } + public bool? udp_fragment { get; set; } + public Rule4Sbox? domain_resolver { get; set; } // or string + public string? network_strategy { get; set; } + public List? network_type { get; set; } + public List? 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; } +} diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 992adb94..5f64c8b1 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // 此代码由工具生成。 // 运行时版本:4.0.30319.42000 @@ -186,6 +186,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Please fill in the correct custom config 的本地化字符串。 + /// + public static string FillCorrectConfigText { + get { + return ResourceManager.GetString("FillCorrectConfigText", resourceCulture); + } + } + /// /// 查找类似 Please fill in the correct custom DNS 的本地化字符串。 /// @@ -654,6 +663,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Add [Anytls] Configuration 的本地化字符串。 + /// + public static string menuAddAnytlsServer { + get { + return ResourceManager.GetString("menuAddAnytlsServer", resourceCulture); + } + } + /// /// 查找类似 Add a custom configuration Configuration 的本地化字符串。 /// @@ -843,6 +861,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Custom Config 的本地化字符串。 + /// + public static string menuCustomConfig { + get { + return ResourceManager.GetString("menuCustomConfig", resourceCulture); + } + } + /// /// 查找类似 DNS Settings 的本地化字符串。 /// @@ -2211,6 +2238,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Do Not Add Non-Proxy Protocol Outbound 的本地化字符串。 + /// + public static string TbAddProxyProtocolOutboundOnly { + get { + return ResourceManager.GetString("TbAddProxyProtocolOutboundOnly", resourceCulture); + } + } + /// /// 查找类似 Address 的本地化字符串。 /// @@ -2328,6 +2364,42 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Enable Custom Config 的本地化字符串。 + /// + public static string TbCustomConfigEnable { + get { + return ResourceManager.GetString("TbCustomConfigEnable", resourceCulture); + } + } + + /// + /// 查找类似 sing-box Custom Config 的本地化字符串。 + /// + public static string TbCustomConfigSingbox { + get { + return ResourceManager.GetString("TbCustomConfigSingbox", resourceCulture); + } + } + + /// + /// 查找类似 Enable Custom DNS 的本地化字符串。 + /// + public static string TbCustomDNSEnable { + get { + return ResourceManager.GetString("TbCustomDNSEnable", resourceCulture); + } + } + + /// + /// 查找类似 Custom DNS Enabled, This Page's Settings Invalid 的本地化字符串。 + /// + public static string TbCustomDNSEnabledPageInvalid { + get { + return ResourceManager.GetString("TbCustomDNSEnabledPageInvalid", resourceCulture); + } + } + /// /// 查找类似 Display GUI 的本地化字符串。 /// @@ -2634,6 +2706,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 v2ray Custom Config 的本地化字符串。 + /// + public static string TbRayCustomConfig { + get { + return ResourceManager.GetString("TbRayCustomConfig", resourceCulture); + } + } + + /// + /// 查找类似 Add Outbound Config Only, routing.balancers and routing.rules.outboundTag 的本地化字符串。 + /// + public static string TbRayCustomConfigDesc { + get { + return ResourceManager.GetString("TbRayCustomConfigDesc", resourceCulture); + } + } + /// /// 查找类似 Alias (remarks) 的本地化字符串。 /// @@ -2751,6 +2841,78 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Add Outbound and Endpoint Config Only 的本地化字符串。 + /// + public static string TbSBCustomConfigDesc { + get { + return ResourceManager.GetString("TbSBCustomConfigDesc", resourceCulture); + } + } + + /// + /// 查找类似 sing-box Direct Resolution Strategy 的本地化字符串。 + /// + public static string TbSBDirectResolveStrategy { + get { + return ResourceManager.GetString("TbSBDirectResolveStrategy", resourceCulture); + } + } + + /// + /// 查找类似 The sing-box DoH resolution server can be overwritten 的本地化字符串。 + /// + public static string TbSBDoHOverride { + get { + return ResourceManager.GetString("TbSBDoHOverride", resourceCulture); + } + } + + /// + /// 查找类似 sing-box DoH Resolver Server 的本地化字符串。 + /// + public static string TbSBDoHResolverServer { + get { + return ResourceManager.GetString("TbSBDoHResolverServer", resourceCulture); + } + } + + /// + /// 查找类似 Fallback DNS Resolution, Suggest IP 的本地化字符串。 + /// + public static string TbSBFallbackDNSResolve { + get { + return ResourceManager.GetString("TbSBFallbackDNSResolve", resourceCulture); + } + } + + /// + /// 查找类似 Resolve Outbound Domains 的本地化字符串。 + /// + public static string TbSBOutboundDomainResolve { + get { + return ResourceManager.GetString("TbSBOutboundDomainResolve", resourceCulture); + } + } + + /// + /// 查找类似 Outbound DNS Resolution (sing-box) 的本地化字符串。 + /// + public static string TbSBOutboundsResolverDNS { + get { + return ResourceManager.GetString("TbSBOutboundsResolverDNS", resourceCulture); + } + } + + /// + /// 查找类似 sing-box Remote Resolution Strategy 的本地化字符串。 + /// + public static string TbSBRemoteResolveStrategy { + get { + return ResourceManager.GetString("TbSBRemoteResolveStrategy", resourceCulture); + } + } + /// /// 查找类似 Encryption method (security) 的本地化字符串。 /// @@ -3534,6 +3696,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Set Upstream Proxy Tag 的本地化字符串。 + /// + public static string TbSetUpstreamProxyDetour { + get { + return ResourceManager.GetString("TbSetUpstreamProxyDetour", resourceCulture); + } + } + /// /// 查找类似 Short Id 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index eb9ae271..77ab1f56 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1401,4 +1401,31 @@ Mldsa65Verify + + Add [Anytls] Configuration + + + Custom Config + + + Enable Custom Config + + + v2ray Custom Config + + + sing-box Custom Config + + + Add Outbound Config Only, routing.balancers and routing.rules.outboundTag + + + Add Outbound and Endpoint Config Only + + + Do Not Add Non-Proxy Protocol Outbound + + + Set Upstream Proxy Tag + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 0d45540e..02fd64aa 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1401,4 +1401,31 @@ Mldsa65Verify + + [Anytls] konfiguráció hozzáadása + + + Custom Config + + + Enable Custom Config + + + v2ray Custom Config + + + sing-box Custom Config + + + Add Outbound Config Only, routing.balancers and routing.rules.outboundTag + + + Add Outbound and Endpoint Config Only + + + Do Not Add Non-Proxy Protocol Outbound + + + Set Upstream Proxy Tag + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 03e9b124..87eb1164 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1401,4 +1401,31 @@ Mldsa65Verify + + Add [Anytls] Configuration + + + Custom Config + + + Enable Custom Config + + + v2ray Custom Config + + + sing-box Custom Config + + + Add Outbound Config Only, routing.balancers and routing.rules.outboundTag + + + Add Outbound and Endpoint Config Only + + + Do Not Add Non-Proxy Protocol Outbound + + + Set Upstream Proxy Tag + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 75a596aa..c8307960 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1401,4 +1401,31 @@ Mldsa65Verify + + Добавить сервер [Anytls] + + + Custom Config + + + Enable Custom Config + + + v2ray Custom Config + + + sing-box Custom Config + + + Add Outbound Config Only, routing.balancers and routing.rules.outboundTag + + + Add Outbound and Endpoint Config Only + + + Do Not Add Non-Proxy Protocol Outbound + + + Set Upstream Proxy Tag + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 8766ca8f..f6ae31ad 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1398,4 +1398,31 @@ Mldsa65Verify + + 添加 [Anytls] 配置文件 + + + 自定义配置 + + + 启用自定义配置 + + + v2ray 自定义配置 + + + sing-box 自定义配置 + + + 仅添加出站配置,routing.balancers 和 routing.rules.outboundTag + + + 仅添加出站和端点配置 + + + 不添加非代理协议出站 + + + 设置上游代理 tag + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 9c7a2a3c..39fb0cb7 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1398,4 +1398,31 @@ Mldsa65Verify + + 新增 [Anytls] 設定檔 + + + Custom Config + + + Enable Custom Config + + + v2ray Custom Config + + + sing-box Custom Config + + + Add Outbound Config Only, routing.balancers and routing.rules.outboundTag + + + Add Outbound and Endpoint Config Only + + + Do Not Add Non-Proxy Protocol Outbound + + + Set Upstream Proxy Tag + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig b/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig index f88422a1..b07fd72c 100644 --- a/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig +++ b/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig @@ -1,4 +1,4 @@ -{ +{ "log": { "level": "debug", "timestamp": true @@ -14,22 +14,10 @@ { "type": "direct", "tag": "direct" - }, - { - "type": "block", - "tag": "block" - }, - { - "tag": "dns_out", - "type": "dns" } ], "route": { "rules": [ - { - "protocol": [ "dns" ], - "outbound": "dns_out" - } ] } } \ No newline at end of file diff --git a/v2rayN/ServiceLib/Sample/dns_singbox_normal b/v2rayN/ServiceLib/Sample/dns_singbox_normal index 0921fe64..b32b439c 100644 --- a/v2rayN/ServiceLib/Sample/dns_singbox_normal +++ b/v2rayN/ServiceLib/Sample/dns_singbox_normal @@ -2,28 +2,33 @@ "servers": [ { "tag": "remote", - "address": "tcp://8.8.8.8", - "strategy": "prefer_ipv4", + "type": "tcp", + "server": "8.8.8.8", "detour": "proxy" }, { "tag": "local", - "address": "223.5.5.5", - "strategy": "prefer_ipv4", - "detour": "direct" - }, - { - "tag": "block", - "address": "rcode://success" + "type": "udp", + "server": "223.5.5.5" } ], "rules": [ + { + "domain_suffix": [ + "googleapis.cn", + "gstatic.com" + ], + "server": "remote", + "strategy": "prefer_ipv4" + }, { "rule_set": [ "geosite-cn" ], - "server": "local" + "server": "local", + "strategy": "prefer_ipv4" } ], - "final": "remote" + "final": "remote", + "strategy": "prefer_ipv4" } diff --git a/v2rayN/ServiceLib/Sample/tun_singbox_dns b/v2rayN/ServiceLib/Sample/tun_singbox_dns index d8ca9808..3dd55eef 100644 --- a/v2rayN/ServiceLib/Sample/tun_singbox_dns +++ b/v2rayN/ServiceLib/Sample/tun_singbox_dns @@ -2,29 +2,33 @@ "servers": [ { "tag": "remote", - "address": "tcp://8.8.8.8", - "strategy": "prefer_ipv4", + "type": "tcp", + "server": "8.8.8.8", "detour": "proxy" }, { "tag": "local", - "address": "223.5.5.5", - "strategy": "prefer_ipv4", - "detour": "direct" - }, - { - "tag": "block", - "address": "rcode://success" + "type": "udp", + "server": "223.5.5.5" } ], "rules": [ { - "rule_set": [ - "geosite-cn", - "geosite-geolocation-cn" + "domain_suffix": [ + "googleapis.cn", + "gstatic.com" ], - "server": "local" + "server": "remote", + "strategy": "prefer_ipv4" + }, + { + "rule_set": [ + "geosite-cn" + ], + "server": "local", + "strategy": "prefer_ipv4" } ], - "final": "remote" -} + "final": "remote", + "strategy": "prefer_ipv4" +} \ No newline at end of file diff --git a/v2rayN/ServiceLib/Sample/tun_singbox_rules b/v2rayN/ServiceLib/Sample/tun_singbox_rules index df1dc4ec..a4276134 100644 --- a/v2rayN/ServiceLib/Sample/tun_singbox_rules +++ b/v2rayN/ServiceLib/Sample/tun_singbox_rules @@ -8,13 +8,13 @@ 139, 5353 ], - "outbound": "block" + "action": "reject" }, { "ip_cidr": [ "224.0.0.0/3", "ff00::/8" ], - "outbound": "block" + "action": "reject" } ] \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 79e4c0a2..4996b208 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -1,6 +1,11 @@ using System.Data; using System.Net; using System.Net.NetworkInformation; +using System.Reactive; +using System.Text; +using System.Text.Json.Nodes; +using DynamicData; +using ServiceLib.Models; namespace ServiceLib.Services.CoreConfig; @@ -53,7 +58,18 @@ public class CoreConfigSingboxService await GenInbounds(singboxConfig); - await GenOutbound(node, singboxConfig.outbounds.First()); + if (node.ConfigType == EConfigType.WireGuard) + { + singboxConfig.outbounds.RemoveAt(0); + var endpoints = new Endpoints4Sbox(); + await GenEndpoint(node, endpoints); + endpoints.tag = Global.ProxyTag; + singboxConfig.endpoints = new() { endpoints }; + } + else + { + await GenOutbound(node, singboxConfig.outbounds.First()); + } await GenMoreOutbounds(node, singboxConfig); @@ -67,7 +83,9 @@ public class CoreConfigSingboxService ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; - ret.Data = JsonUtils.Serialize(singboxConfig); + + var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.sing_box); + ret.Data = await ApplyCustomConfig(customConfig, singboxConfig); return ret; } catch (Exception ex) @@ -202,16 +220,29 @@ public class CoreConfigSingboxService continue; } - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(item, outbound); - outbound.tag = Global.ProxyTag + inbound.listen_port.ToString(); - singboxConfig.outbounds.Add(outbound); + var server = await GenServer(item); + if (server is null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + var tag = Global.ProxyTag + inbound.listen_port.ToString(); + server.tag = tag; + if (server is Endpoints4Sbox endpoint) + { + singboxConfig.endpoints ??= new(); + singboxConfig.endpoints.Add(endpoint); + } + else if (server is Outbound4Sbox outbound) + { + singboxConfig.outbounds.Add(outbound); + } //rule Rule4Sbox rule = new() { inbound = new List { inbound.tag }, - outbound = outbound.tag + outbound = tag }; singboxConfig.route.rules.Add(rule); } @@ -275,7 +306,18 @@ public class CoreConfigSingboxService } await GenLog(singboxConfig); - await GenOutbound(node, singboxConfig.outbounds.First()); + if (node.ConfigType == EConfigType.WireGuard) + { + singboxConfig.outbounds.RemoveAt(0); + var endpoints = new Endpoints4Sbox(); + await GenEndpoint(node, endpoints); + endpoints.tag = Global.ProxyTag; + singboxConfig.endpoints = new() { endpoints }; + } + else + { + await GenOutbound(node, singboxConfig.outbounds.First()); + } await GenMoreOutbounds(node, singboxConfig); await GenDnsDomains(null, singboxConfig, null); @@ -383,7 +425,9 @@ public class CoreConfigSingboxService await ConvertGeo2Ruleset(singboxConfig); ret.Success = true; - ret.Data = JsonUtils.Serialize(singboxConfig); + + var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.sing_box); + ret.Data = await ApplyCustomConfig(customConfig, singboxConfig); return ret; } catch (Exception ex) @@ -534,15 +578,6 @@ public class CoreConfigSingboxService singboxConfig.inbounds.Add(inbound); inbound.listen_port = AppHandler.Instance.GetLocalPort(EInboundProtocol.socks); - inbound.sniff = _config.Inbound.First().SniffingEnabled; - inbound.sniff_override_destination = _config.Inbound.First().RouteOnly ? false : _config.Inbound.First().SniffingEnabled; - inbound.domain_strategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox; - - var routing = await ConfigHandler.GetDefaultRouting(_config); - if (routing.DomainStrategy4Singbox.IsNotEmpty()) - { - inbound.domain_strategy = routing.DomainStrategy4Singbox; - } if (_config.Inbound.First().SecondLocalPortEnabled) { @@ -587,8 +622,6 @@ public class CoreConfigSingboxService tunInbound.mtu = _config.TunModeItem.Mtu; tunInbound.strict_route = _config.TunModeItem.StrictRoute; tunInbound.stack = _config.TunModeItem.Stack; - tunInbound.sniff = _config.Inbound.First().SniffingEnabled; - //tunInbound.sniff_override_destination = _config.inbound.First().routeOnly ? false : _config.inbound.First().sniffingEnabled; if (_config.TunModeItem.EnableIPv6Address == false) { tunInbound.address = ["172.18.0.1/30"]; @@ -621,6 +654,17 @@ public class CoreConfigSingboxService outbound.server_port = node.Port; outbound.type = Global.ProtocolTypes[node.ConfigType]; + if (Utils.IsDomain(node.Address)) + { + var item = await AppHandler.Instance.GetDNSItem(ECoreType.sing_box); + var localDnsAddress = string.IsNullOrEmpty(item?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : item?.DomainDNSAddress; + outbound.domain_resolver = new() + { + server = localDnsAddress.StartsWith("tag://") ? localDnsAddress.Substring(6) : "local_resolver", + strategy = string.IsNullOrEmpty(item?.DomainStrategy4Freedom) ? null : item?.DomainStrategy4Freedom + }; + } + switch (node.ConfigType) { case EConfigType.VMess: @@ -730,13 +774,9 @@ public class CoreConfigSingboxService outbound.congestion_control = node.HeaderType; break; } - case EConfigType.WireGuard: + case EConfigType.Anytls: { - outbound.private_key = node.Id; - outbound.peer_public_key = node.PublicKey; - outbound.reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(); - outbound.local_address = Utils.String2List(node.RequestHost); - outbound.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(); + outbound.password = node.Id; break; } } @@ -752,6 +792,76 @@ public class CoreConfigSingboxService return 0; } + private async Task 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 GenServer(ProfileItem node) + { + try + { + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); + if (node.ConfigType == EConfigType.WireGuard) + { + var endpoint = JsonUtils.Deserialize(txtOutbound); + await GenEndpoint(node, endpoint); + return endpoint; + } + else + { + var outbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(node, outbound); + return outbound; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return await Task.FromResult(null); + } + private async Task GenOutboundMux(ProfileItem node, Outbound4Sbox outbound) { try @@ -918,7 +1028,8 @@ public class CoreConfigSingboxService } //current proxy - var outbound = singboxConfig.outbounds.First(); + BaseServer4Sbox? outbound = singboxConfig.endpoints?.FirstOrDefault(t => t.tag == Global.ProxyTag) == null ? singboxConfig.outbounds.First() : null; + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); //Previous proxy @@ -927,17 +1038,32 @@ public class CoreConfigSingboxService if (prevNode is not null && prevNode.ConfigType != EConfigType.Custom) { - var prevOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(prevNode, prevOutbound); prevOutboundTag = $"prev-{Global.ProxyTag}"; - prevOutbound.tag = prevOutboundTag; - singboxConfig.outbounds.Add(prevOutbound); + var prevServer = await GenServer(prevNode); + prevServer.tag = prevOutboundTag; + if (prevServer is Endpoints4Sbox endpoint) + { + singboxConfig.endpoints ??= new(); + singboxConfig.endpoints.Add(endpoint); + } + else if (prevServer is Outbound4Sbox outboundPrev) + { + singboxConfig.outbounds.Add(outboundPrev); + } } - var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag); + var nextServer = await GenChainOutbounds(subItem, outbound, prevOutboundTag); - if (nextOutbound is not null) + if (nextServer is not null) { - singboxConfig.outbounds.Insert(0, nextOutbound); + if (nextServer is Endpoints4Sbox endpoint) + { + singboxConfig.endpoints ??= new(); + singboxConfig.endpoints.Insert(0, endpoint); + } + else if (nextServer is Outbound4Sbox outboundNext) + { + singboxConfig.outbounds.Insert(0, outboundNext); + } } } catch (Exception ex) @@ -960,11 +1086,13 @@ public class CoreConfigSingboxService } var resultOutbounds = new List(); + var resultEndpoints = new List(); // For endpoints var prevOutbounds = new List(); // Separate list for prev outbounds + var prevEndpoints = new List(); // Separate list for prev endpoints var proxyTags = new List(); // For selector and urltest outbounds // Cache for chain proxies to avoid duplicate generation - var nextProxyCache = new Dictionary(); + var nextProxyCache = new Dictionary(); var prevProxyTags = new Dictionary(); // Map from profile name to tag int prevIndex = 0; // Index for prev outbounds @@ -976,19 +1104,18 @@ public class CoreConfigSingboxService // Handle proxy chain string? prevTag = null; - var currentOutbound = JsonUtils.Deserialize(txtOutbound); - var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null); - if (nextOutbound != null) + var currentServer = await GenServer(node); + var nextServer = nextProxyCache.GetValueOrDefault(node.Subid, null); + if (nextServer != null) { - nextOutbound = JsonUtils.DeepCopy(nextOutbound); + nextServer = JsonUtils.DeepCopy(nextServer); } var subItem = await AppHandler.Instance.GetSubItem(node.Subid); // current proxy - await GenOutbound(node, currentOutbound); - currentOutbound.tag = $"{Global.ProxyTag}-{index}"; - proxyTags.Add(currentOutbound.tag); + currentServer.tag = $"{Global.ProxyTag}-{index}"; + proxyTags.Add(currentServer.tag); if (!node.Subid.IsNullOrEmpty()) { @@ -1011,18 +1138,32 @@ public class CoreConfigSingboxService prevProxyTags[node.Subid] = prevTag; } - nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound); + nextServer = await GenChainOutbounds(subItem, currentServer, prevTag, nextServer); if (!nextProxyCache.ContainsKey(node.Subid)) { - nextProxyCache[node.Subid] = nextOutbound; + nextProxyCache[node.Subid] = nextServer; } } - if (nextOutbound is not null) + if (nextServer is not null) { - resultOutbounds.Add(nextOutbound); + if (nextServer is Endpoints4Sbox nextEndpoint) + { + resultEndpoints.Add(nextEndpoint); + } + else if (nextServer is Outbound4Sbox nextOutbound) + { + resultOutbounds.Add(nextOutbound); + } + } + if (currentServer is Endpoints4Sbox currentEndpoint) + { + resultEndpoints.Add(currentEndpoint); + } + else if (currentServer is Outbound4Sbox currentOutbound) + { + resultOutbounds.Add(currentOutbound); } - resultOutbounds.Add(currentOutbound); } // Add urltest outbound (auto selection based on latency) @@ -1055,6 +1196,9 @@ public class CoreConfigSingboxService resultOutbounds.AddRange(prevOutbounds); resultOutbounds.AddRange(singboxConfig.outbounds); singboxConfig.outbounds = resultOutbounds; + singboxConfig.endpoints ??= new List(); + resultEndpoints.AddRange(singboxConfig.endpoints); + singboxConfig.endpoints = resultEndpoints; } catch (Exception ex) { @@ -1076,7 +1220,7 @@ public class CoreConfigSingboxService /// /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. /// - private async Task GenChainOutbounds(SubItem subItem, Outbound4Sbox outbound, string? prevOutboundTag, Outbound4Sbox? nextOutbound = null) + private async Task GenChainOutbounds(SubItem subItem, BaseServer4Sbox outbound, string? prevOutboundTag, BaseServer4Sbox? nextOutbound = null) { try { @@ -1092,11 +1236,7 @@ public class CoreConfigSingboxService if (nextNode is not null && nextNode.ConfigType != EConfigType.Custom) { - if (nextOutbound == null) - { - nextOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(nextNode, nextOutbound); - } + nextOutbound ??= await GenServer(nextNode); nextOutbound.tag = outbound.tag; outbound.tag = $"mid-{outbound.tag}"; @@ -1115,7 +1255,7 @@ public class CoreConfigSingboxService { try { - var dnsOutbound = "dns_out"; + singboxConfig.route.final = Global.ProxyTag; if (_config.TunModeItem.EnableTun) { @@ -1131,7 +1271,7 @@ public class CoreConfigSingboxService singboxConfig.route.rules.Add(new() { port = new() { 53 }, - outbound = dnsOutbound, + action = "hijack-dns", process_name = lstDnsExe }); @@ -1142,13 +1282,25 @@ public class CoreConfigSingboxService }); } - if (!_config.Inbound.First().SniffingEnabled) + if (_config.Inbound.First().SniffingEnabled) { singboxConfig.route.rules.Add(new() { - port = [53], - network = ["udp"], - outbound = dnsOutbound + action = "sniff" + }); + singboxConfig.route.rules.Add(new() + { + protocol = new() { "dns" }, + action = "hijack-dns" + }); + } + else + { + singboxConfig.route.rules.Add(new() + { + port = new() { 53 }, + network = new() { "udp" }, + action = "hijack-dns" }); } @@ -1163,7 +1315,24 @@ public class CoreConfigSingboxService clash_mode = ERuleMode.Global.ToString() }); + var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox; + var defaultRouting = await ConfigHandler.GetDefaultRouting(_config); + if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty()) + { + domainStrategy = defaultRouting.DomainStrategy4Singbox; + } + var resolveRule = new Rule4Sbox + { + action = "resolve", + strategy = domainStrategy + }; + if (_config.RoutingBasicItem.DomainStrategy == "IPOnDemand") + { + singboxConfig.route.rules.Add(resolveRule); + } + var routing = await ConfigHandler.GetDefaultRouting(_config); + var ipRules = new List(); if (routing != null) { var rules = JsonUtils.Deserialize>(routing.RuleSet); @@ -1172,9 +1341,21 @@ public class CoreConfigSingboxService if (item.Enabled) { await GenRoutingUserRule(item, singboxConfig); + if (item.Ip != null && item.Ip.Count > 0) + { + ipRules.Add(item); + } } } } + if (_config.RoutingBasicItem.DomainStrategy == "IPIfNonMatch") + { + singboxConfig.route.rules.Add(resolveRule); + foreach (var item in ipRules) + { + await GenRoutingUserRule(item, singboxConfig); + } + } } catch (Exception ex) { @@ -1222,10 +1403,15 @@ public class CoreConfigSingboxService item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig); var rules = singboxConfig.route.rules; - var rule = new Rule4Sbox() + var rule = new Rule4Sbox(); + if (item.OutboundTag == "block") { - outbound = item.OutboundTag, - }; + rule.action = "reject"; + } + else + { + rule.outbound = item.OutboundTag; + } if (item.Port.IsNotEmpty()) { @@ -1349,24 +1535,28 @@ public class CoreConfigSingboxService { return false; } - else if (address.StartsWith("geoip:!")) - { - return false; - } else if (address.Equals("geoip:private")) { rule.ip_is_private = true; } else if (address.StartsWith("geoip:")) { - if (rule.geoip is null) - { rule.geoip = new(); } + rule.geoip ??= new(); rule.geoip?.Add(address.Substring(6)); } + else if (address.Equals("geoip:!private")) + { + rule.ip_is_private = false; + } + else if (address.StartsWith("geoip:!")) + { + rule.geoip ??= new(); + rule.geoip?.Add(address.Substring(6)); + rule.invert = true; + } else { - if (rule.ip_cidr is null) - { rule.ip_cidr = new(); } + rule.ip_cidr ??= new(); rule.ip_cidr?.Add(address); } return true; @@ -1386,13 +1576,24 @@ public class CoreConfigSingboxService return Global.ProxyTag; } - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(node, outbound); - outbound.tag = Global.ProxyTag + node.IndexId.ToString(); - singboxConfig.outbounds.Add(outbound); + var server = await GenServer(node); + if (server is null) + { + return Global.ProxyTag; + } - return outbound.tag; + server.tag = Global.ProxyTag + node.IndexId.ToString(); + if (server is Endpoints4Sbox endpoint) + { + singboxConfig.endpoints ??= new(); + singboxConfig.endpoints.Add(endpoint); + } + else if (server is Outbound4Sbox outbound) + { + singboxConfig.outbounds.Add(outbound); + } + + return server.tag; } private async Task GenDns(ProfileItem? node, SingboxConfig singboxConfig) @@ -1417,7 +1618,14 @@ public class CoreConfigSingboxService } singboxConfig.dns = dns4Sbox; - await GenDnsDomains(node, singboxConfig, item); + if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty()) + { + await GenDnsDomains(node, singboxConfig, item); + } + else + { + await GenDnsDomainsLegacy(node, singboxConfig, item); + } } catch (Exception ex) { @@ -1432,6 +1640,75 @@ public class CoreConfigSingboxService dns4Sbox.servers ??= []; dns4Sbox.rules ??= []; + var tag = "local_resolver"; + var localDnsAddress = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.SingboxDomainDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress; + + if (localDnsAddress.StartsWith("tag://")) + { + tag = localDnsAddress.Substring(6); + + var localDnsTag = "local_local"; + + dns4Sbox.servers.Add(new() + { + tag = localDnsTag, + type = "local" + }); + + dns4Sbox.rules.Insert(0, new() + { + server = localDnsTag, + clash_mode = ERuleMode.Direct.ToString() + }); + } + else + { + var (dnsType, dnsHost, dnsPort, dnsPath) = ParseDnsAddress(localDnsAddress); + + dns4Sbox.servers.Add(new() + { + tag = tag, + type = dnsType, + server = dnsHost, + Interface = dnsType == "dhcp" ? dnsHost : null, + server_port = dnsPort, + path = dnsPath + }); + + dns4Sbox.rules.Insert(0, new() + { + server = tag, + clash_mode = ERuleMode.Direct.ToString() + }); + } + + dns4Sbox.rules.Insert(0, new() + { + server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote", + clash_mode = ERuleMode.Global.ToString() + }); + + //Tun2SocksAddress + if (_config.TunModeItem.EnableTun && node?.ConfigType == EConfigType.SOCKS && Utils.IsDomain(node?.Sni)) + { + dns4Sbox.rules.Insert(0, new() + { + server = tag, + domain = [node?.Sni], + strategy = string.IsNullOrEmpty(dNSItem?.DomainStrategy4Freedom) ? null : dNSItem?.DomainStrategy4Freedom + }); + } + + singboxConfig.dns = dns4Sbox; + return await Task.FromResult(0); + } + + private async Task GenDnsDomainsLegacy(ProfileItem? node, SingboxConfig singboxConfig, DNSItem? dNSItem) + { + var dns4Sbox = singboxConfig.dns ?? new(); + dns4Sbox.servers ??= []; + dns4Sbox.rules ??= []; + var tag = "local_local"; dns4Sbox.servers.Add(new() { @@ -1479,6 +1756,91 @@ public class CoreConfigSingboxService return await Task.FromResult(0); } + private (string type, string? host, int? port, string? path) ParseDnsAddress(string address) + { + string type = "udp"; + string? host = null; + int? port = null; + string? path = null; + + if (address is "local" or "localhost") + { + return ("local", null, null, null); + } + + if (address.StartsWith("dhcp://", StringComparison.OrdinalIgnoreCase)) + { + string interface_name = address.Substring(7); + return ("dhcp", interface_name == "auto" ? null : interface_name, null, null); + } + + if (!address.Contains("://")) + { + // udp dns + host = address; + return (type, host, port, path); + } + + try + { + int protocolEndIndex = address.IndexOf("://", StringComparison.Ordinal); + type = address.Substring(0, protocolEndIndex).ToLower(); + + var uri = new Uri(address); + host = uri.Host; + + if (!uri.IsDefaultPort) + { + port = uri.Port; + } + + if ((type == "https" || type == "h3") && !string.IsNullOrEmpty(uri.AbsolutePath) && uri.AbsolutePath != "/") + { + path = uri.AbsolutePath; + } + } + catch (UriFormatException) + { + int protocolEndIndex = address.IndexOf("://", StringComparison.Ordinal); + if (protocolEndIndex > 0) + { + type = address.Substring(0, protocolEndIndex).ToLower(); + string remaining = address.Substring(protocolEndIndex + 3); + + int portIndex = remaining.IndexOf(':'); + int pathIndex = remaining.IndexOf('/'); + + if (portIndex > 0) + { + host = remaining.Substring(0, portIndex); + string portPart = pathIndex > portIndex + ? remaining.Substring(portIndex + 1, pathIndex - portIndex - 1) + : remaining.Substring(portIndex + 1); + + if (int.TryParse(portPart, out int parsedPort)) + { + port = parsedPort; + } + } + else if (pathIndex > 0) + { + host = remaining.Substring(0, pathIndex); + } + else + { + host = remaining; + } + + if (pathIndex > 0 && (type == "https" || type == "h3")) + { + path = remaining.Substring(pathIndex); + } + } + } + + return (type, host, port, path); + } + private async Task GenExperimental(SingboxConfig singboxConfig) { //if (_config.guiItem.enableStatistics) @@ -1614,5 +1976,61 @@ public class CoreConfigSingboxService return 0; } + private async Task ApplyCustomConfig(CustomConfigItem customConfig, SingboxConfig singboxConfig) + { + var customConfigItem = customConfig.Config; + if (_config.TunModeItem.EnableTun) + { + customConfigItem = customConfig.TunConfig; + } + + if (!customConfig.Enabled || customConfigItem.IsNullOrEmpty()) + { + return JsonUtils.Serialize(singboxConfig); + } + + var customConfigNode = JsonNode.Parse(customConfigItem); + if (customConfigNode == null) + { + return JsonUtils.Serialize(singboxConfig); + } + + // Process outbounds + var customOutboundsNode = customConfigNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray(); + foreach (var outbound in singboxConfig.outbounds) + { + if (outbound.type.ToLower() is "direct" or "block") + { + if (customConfig.AddProxyOnly == true) + { + continue; + } + } + else if (outbound.detour.IsNullOrEmpty() && (!customConfig.ProxyDetour.IsNullOrEmpty()) && !Utils.IsPrivateNetwork(outbound.server ?? string.Empty)) + { + outbound.detour = customConfig.ProxyDetour; + } + customOutboundsNode.Add(JsonUtils.DeepCopy(outbound)); + } + customConfigNode["outbounds"] = customOutboundsNode; + + // Process endpoints + if (singboxConfig.endpoints != null && singboxConfig.endpoints.Count > 0) + { + var customEndpointsNode = customConfigNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray(); + foreach (var endpoint in singboxConfig.endpoints) + { + if (endpoint.detour.IsNullOrEmpty() && (!customConfig.ProxyDetour.IsNullOrEmpty())) + { + endpoint.detour = customConfig.ProxyDetour; + } + customEndpointsNode.Add(JsonUtils.DeepCopy(endpoint)); + } + customConfigNode["endpoints"] = customEndpointsNode; + } + + return await Task.FromResult(JsonUtils.Serialize(customConfigNode)); + } + #endregion private gen function } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index 2e4f5842..c6d0aeb3 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -1,6 +1,8 @@ using System.Net; using System.Net.NetworkInformation; using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using ServiceLib.Models; namespace ServiceLib.Services.CoreConfig; @@ -66,7 +68,9 @@ public class CoreConfigV2rayService ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; - ret.Data = JsonUtils.Serialize(v2rayConfig); + + var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.Xray); + ret.Data = await ApplyCustomConfig(customConfig, v2rayConfig); return ret; } catch (Exception ex) @@ -120,7 +124,7 @@ public class CoreConfigV2rayService { continue; } - if (it.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC) + if (it.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) { continue; } @@ -195,7 +199,9 @@ public class CoreConfigV2rayService } ret.Success = true; - ret.Data = JsonUtils.Serialize(v2rayConfig); + + var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.Xray); + ret.Data = await ApplyCustomConfig(customConfig, v2rayConfig, true); return ret; } catch (Exception ex) @@ -640,7 +646,8 @@ public class CoreConfigV2rayService if (node == null || node.ConfigType == EConfigType.Custom || node.ConfigType == EConfigType.Hysteria2 - || node.ConfigType == EConfigType.TUIC) + || node.ConfigType == EConfigType.TUIC + || node.ConfigType == EConfigType.Anytls) { return Global.ProxyTag; } @@ -1222,6 +1229,7 @@ public class CoreConfigV2rayService && prevNode.ConfigType != EConfigType.Custom && prevNode.ConfigType != EConfigType.Hysteria2 && prevNode.ConfigType != EConfigType.TUIC + && prevNode.ConfigType != EConfigType.Anytls && Utils.IsDomain(prevNode.Address)) { domainList.Add(prevNode.Address); @@ -1233,6 +1241,7 @@ public class CoreConfigV2rayService && nextNode.ConfigType != EConfigType.Custom && nextNode.ConfigType != EConfigType.Hysteria2 && nextNode.ConfigType != EConfigType.TUIC + && nextNode.ConfigType != EConfigType.Anytls && Utils.IsDomain(nextNode.Address)) { domainList.Add(nextNode.Address); @@ -1350,7 +1359,8 @@ public class CoreConfigV2rayService if (prevNode is not null && prevNode.ConfigType != EConfigType.Custom && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC) + && prevNode.ConfigType != EConfigType.TUIC + && prevNode.ConfigType != EConfigType.Anytls) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -1425,7 +1435,8 @@ public class CoreConfigV2rayService if (prevNode is not null && prevNode.ConfigType != EConfigType.Custom && prevNode.ConfigType != EConfigType.Hysteria2 - && prevNode.ConfigType != EConfigType.TUIC) + && prevNode.ConfigType != EConfigType.TUIC + && prevNode.ConfigType != EConfigType.Anytls) { var prevOutbound = JsonUtils.Deserialize(txtOutbound); await GenOutbound(prevNode, prevOutbound); @@ -1494,7 +1505,8 @@ public class CoreConfigV2rayService if (nextNode is not null && nextNode.ConfigType != EConfigType.Custom && nextNode.ConfigType != EConfigType.Hysteria2 - && nextNode.ConfigType != EConfigType.TUIC) + && nextNode.ConfigType != EConfigType.TUIC + && nextNode.ConfigType != EConfigType.Anytls) { if (nextOutbound == null) { @@ -1564,5 +1576,83 @@ public class CoreConfigV2rayService return await Task.FromResult(0); } + private async Task ApplyCustomConfig(CustomConfigItem customConfig, V2rayConfig v2rayConfig, bool handleBalancerAndRules = false) + { + if (!customConfig.Enabled || customConfig.Config.IsNullOrEmpty()) + { + return JsonUtils.Serialize(v2rayConfig); + } + + var customConfigNode = JsonNode.Parse(customConfig.Config); + if (customConfigNode == null) + { + return JsonUtils.Serialize(v2rayConfig); + } + + // Handle balancer and rules modifications (for multiple load scenarios) + if (handleBalancerAndRules && v2rayConfig.routing?.balancers?.Count > 0) + { + var balancer = v2rayConfig.routing.balancers.First(); + + // Modify existing rules in custom config + var rulesNode = customConfigNode["routing"]?["rules"]; + if (rulesNode != null) + { + foreach (var rule in rulesNode.AsArray()) + { + if (rule["outboundTag"]?.GetValue() == Global.ProxyTag) + { + rule.AsObject().Remove("outboundTag"); + rule["balancerTag"] = balancer.tag; + } + } + } + + // Ensure routing node exists + if (customConfigNode["routing"] == null) + { + customConfigNode["routing"] = new JsonObject(); + } + + // Handle balancers - append instead of override + if (customConfigNode["routing"]["balancers"] is JsonArray customBalancersNode) + { + if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)) is JsonArray newBalancers) + { + foreach (var balancerNode in newBalancers) + { + customBalancersNode.Add(balancerNode?.DeepClone()); + } + } + } + else + { + customConfigNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)); + } + } + + // Handle outbounds - append instead of override + var customOutboundsNode = customConfigNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray(); + foreach (var outbound in v2rayConfig.outbounds) + { + if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom") + { + if (customConfig.AddProxyOnly == true) + { + continue; + } + } + else if ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() == true) && (!customConfig.ProxyDetour.IsNullOrEmpty()) && !(Utils.IsPrivateNetwork(outbound.settings?.servers?.FirstOrDefault()?.address ?? string.Empty) || Utils.IsPrivateNetwork(outbound.settings?.vnext?.FirstOrDefault()?.address ?? string.Empty))) + { + outbound.streamSettings ??= new StreamSettings4Ray(); + outbound.streamSettings.sockopt ??= new Sockopt4Ray(); + outbound.streamSettings.sockopt.dialerProxy = customConfig.ProxyDetour; + } + customOutboundsNode.Add(JsonUtils.DeepCopy(outbound)); + } + + return await Task.FromResult(JsonUtils.Serialize(customConfigNode)); + } + #endregion private gen function } diff --git a/v2rayN/ServiceLib/Services/SpeedtestService.cs b/v2rayN/ServiceLib/Services/SpeedtestService.cs index 998cedcc..9c97a217 100644 --- a/v2rayN/ServiceLib/Services/SpeedtestService.cs +++ b/v2rayN/ServiceLib/Services/SpeedtestService.cs @@ -358,8 +358,8 @@ public class SpeedtestService private List> GetTestBatchItem(List lstSelected, int pageSize) { List> lstTest = new(); - var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC)).ToList(); - var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC).ToList(); + var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)).ToList(); + var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls).ToList(); for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++) { diff --git a/v2rayN/ServiceLib/ViewModels/CustomConfigViewModel.cs b/v2rayN/ServiceLib/ViewModels/CustomConfigViewModel.cs new file mode 100644 index 00000000..75d7d5be --- /dev/null +++ b/v2rayN/ServiceLib/ViewModels/CustomConfigViewModel.cs @@ -0,0 +1,110 @@ +using System.Reactive; +using DynamicData.Binding; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; + +namespace ServiceLib.ViewModels; +public class CustomConfigViewModel : MyReactiveObject +{ + #region Reactive + [Reactive] + public bool EnableCustomConfig4Ray { get; set; } + + [Reactive] + public bool EnableCustomConfig4Singbox { get; set; } + + [Reactive] + public string CustomConfig4Ray { get; set; } + + [Reactive] + public string CustomConfig4Singbox { get; set; } + + [Reactive] + public string CustomTunConfig4Singbox { get; set; } + + [Reactive] + public bool AddProxyOnly4Ray { get; set; } + + [Reactive] + public bool AddProxyOnly4Singbox { get; set; } + + [Reactive] + public string ProxyDetour4Ray { get; set; } + + [Reactive] + public string ProxyDetour4Singbox { get; set; } + + public ReactiveCommand SaveCmd { get; } + #endregion Reactive + + public CustomConfigViewModel(Func>? updateView) + { + _config = AppHandler.Instance.Config; + _updateView = updateView; + SaveCmd = ReactiveCommand.CreateFromTask(async () => + { + await SaveSettingAsync(); + }); + + _ = Init(); + } + private async Task Init() + { + var item = await AppHandler.Instance.GetCustomConfigItem(ECoreType.Xray); + EnableCustomConfig4Ray = item?.Enabled ?? false; + CustomConfig4Ray = item?.Config ?? string.Empty; + AddProxyOnly4Ray = item?.AddProxyOnly ?? false; + ProxyDetour4Ray = item?.ProxyDetour ?? string.Empty; + + var item2 = await AppHandler.Instance.GetCustomConfigItem(ECoreType.sing_box); + EnableCustomConfig4Singbox = item2?.Enabled ?? false; + CustomConfig4Singbox = item2?.Config ?? string.Empty; + CustomTunConfig4Singbox = item2?.TunConfig ?? string.Empty; + AddProxyOnly4Singbox = item2?.AddProxyOnly ?? false; + ProxyDetour4Singbox = item2?.ProxyDetour ?? string.Empty; + } + + private async Task SaveSettingAsync() + { + if (!await SaveXrayConfigAsync()) + return; + + if (!await SaveSingboxConfigAsync()) + return; + + NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess); + _ = _updateView?.Invoke(EViewAction.CloseWindow, null); + } + + private async Task SaveXrayConfigAsync() + { + var item = await AppHandler.Instance.GetCustomConfigItem(ECoreType.Xray); + item.Enabled = EnableCustomConfig4Ray; + item.Config = null; + + item.Config = CustomConfig4Ray; + + item.AddProxyOnly = AddProxyOnly4Ray; + item.ProxyDetour = ProxyDetour4Ray; + + await ConfigHandler.SaveCustomConfigItem(_config, item); + return true; + } + + private async Task SaveSingboxConfigAsync() + { + var item = await AppHandler.Instance.GetCustomConfigItem(ECoreType.sing_box); + item.Enabled = EnableCustomConfig4Singbox; + item.Config = null; + item.TunConfig = null; + + item.Config = CustomConfig4Singbox; + item.TunConfig = CustomTunConfig4Singbox; + + item.AddProxyOnly = AddProxyOnly4Singbox; + item.ProxyDetour = ProxyDetour4Singbox; + + await ConfigHandler.SaveCustomConfigItem(_config, item); + return true; + } +} diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index 7bae19be..71536ab2 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -20,6 +20,7 @@ public class MainWindowViewModel : MyReactiveObject public ReactiveCommand AddHysteria2ServerCmd { get; } public ReactiveCommand AddTuicServerCmd { get; } public ReactiveCommand AddWireguardServerCmd { get; } + public ReactiveCommand AddAnytlsServerCmd { get; } public ReactiveCommand AddCustomServerCmd { get; } public ReactiveCommand AddServerViaClipboardCmd { get; } public ReactiveCommand AddServerViaScanCmd { get; } @@ -38,6 +39,7 @@ public class MainWindowViewModel : MyReactiveObject public ReactiveCommand RoutingSettingCmd { get; } public ReactiveCommand DNSSettingCmd { get; } + public ReactiveCommand CustomConfigCmd { get; } public ReactiveCommand GlobalHotkeySettingCmd { get; } public ReactiveCommand RebootAsAdminCmd { get; } public ReactiveCommand ClearServerStatisticsCmd { get; } @@ -111,6 +113,10 @@ public class MainWindowViewModel : MyReactiveObject { await AddServerAsync(true, EConfigType.WireGuard); }); + AddAnytlsServerCmd = ReactiveCommand.CreateFromTask(async () => + { + await AddServerAsync(true, EConfigType.Anytls); + }); AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () => { await AddServerAsync(true, EConfigType.Custom); @@ -164,6 +170,10 @@ public class MainWindowViewModel : MyReactiveObject { await DNSSettingAsync(); }); + CustomConfigCmd = ReactiveCommand.CreateFromTask(async () => + { + await CustomConfigAsync(); + }); GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () => { if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true) @@ -215,6 +225,7 @@ public class MainWindowViewModel : MyReactiveObject await ConfigHandler.InitBuiltinRouting(_config); await ConfigHandler.InitBuiltinDNS(_config); + await ConfigHandler.InitBuiltinCustomConfig(_config); await ProfileExHandler.Instance.Init(); await CoreHandler.Instance.Init(_config, UpdateHandler); TaskHandler.Instance.RegUpdateTask(_config, UpdateTaskHandler); @@ -503,6 +514,15 @@ public class MainWindowViewModel : MyReactiveObject } } + private async Task CustomConfigAsync() + { + var ret = await _updateView?.Invoke(EViewAction.CustomConfigWindow, null); + if (ret == true) + { + await Reload(); + } + } + public async Task RebootAsAdmin() { ProcUtils.RebootAsAdmin(); diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index da56d617..56cc82a0 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -533,6 +533,26 @@ HorizontalAlignment="Left" Watermark="1500" /> + + + + + gridTls.IsVisible = false; break; + + case EConfigType.Anytls: + gridAnytls.IsVisible = true; + lstStreamSecurity.Add(Global.StreamSecurityReality); + cmbCoreType.IsEnabled = false; + break; } cmbStreamSecurity.ItemsSource = lstStreamSecurity; @@ -167,6 +173,10 @@ public partial class AddServerWindow : WindowBase this.Bind(ViewModel, vm => vm.SelectedSource.RequestHost, v => v.txtRequestHost9.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId9.Text).DisposeWith(disposables); break; + + case EConfigType.Anytls: + this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId10.Text).DisposeWith(disposables); + break; } 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); diff --git a/v2rayN/v2rayN.Desktop/Views/CustomConfigWindow.axaml b/v2rayN/v2rayN.Desktop/Views/CustomConfigWindow.axaml new file mode 100644 index 00000000..fcfedfe7 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/CustomConfigWindow.axaml @@ -0,0 +1,182 @@ + + + +