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/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 6c93d868..25d90be9 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -2184,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/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/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index ca554860..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 的本地化字符串。 /// @@ -852,6 +861,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Custom Config 的本地化字符串。 + /// + public static string menuCustomConfig { + get { + return ResourceManager.GetString("menuCustomConfig", resourceCulture); + } + } + /// /// 查找类似 DNS Settings 的本地化字符串。 /// @@ -2220,6 +2238,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Do Not Add Non-Proxy Protocol Outbound 的本地化字符串。 + /// + public static string TbAddProxyProtocolOutboundOnly { + get { + return ResourceManager.GetString("TbAddProxyProtocolOutboundOnly", resourceCulture); + } + } + /// /// 查找类似 Address 的本地化字符串。 /// @@ -2337,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 的本地化字符串。 /// @@ -2643,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) 的本地化字符串。 /// @@ -2760,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) 的本地化字符串。 /// @@ -3543,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 bd2cb887..77ab1f56 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1404,4 +1404,28 @@ 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 0fc30153..02fd64aa 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1404,4 +1404,28 @@ [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 7ecfcc98..87eb1164 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1404,4 +1404,28 @@ 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 25f17e0d..c8307960 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1404,4 +1404,28 @@ Добавить сервер [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 95d69e9b..f6ae31ad 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1401,4 +1401,28 @@ 添加 [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 0b23885c..39fb0cb7 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1401,4 +1401,28 @@ 新增 [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/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 80b89654..4996b208 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -2,6 +2,8 @@ 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; @@ -81,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) @@ -421,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) @@ -1970,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 60b4df3e..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) @@ -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) @@ -1570,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/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 36e20a87..71536ab2 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -39,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; } @@ -169,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) @@ -220,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); @@ -508,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/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 @@ + + + +