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 9040d70c..129c67c8 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -2220,6 +2220,54 @@ public class ConfigHandler #endregion Simple 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..23999af5 --- /dev/null +++ b/v2rayN/ServiceLib/Models/CustomConfigItem.cs @@ -0,0 +1,16 @@ +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; } +} diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index be6fea63..82155f4c 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -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 的本地化字符串。 /// @@ -2373,6 +2391,24 @@ 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); + } + } + /// /// 查找类似 Display GUI 的本地化字符串。 /// @@ -2706,6 +2742,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 v2ray Custom Config 的本地化字符串。 + /// + public static string TbRayCustomConfig { + get { + return ResourceManager.GetString("TbRayCustomConfig", resourceCulture); + } + } + + /// + /// 查找类似 Override Outbound Config, routing.balancers and routing.rules.outboundTag Only 的本地化字符串。 + /// + public static string TbRayCustomConfigDesc { + get { + return ResourceManager.GetString("TbRayCustomConfigDesc", resourceCulture); + } + } + /// /// 查找类似 Alias (remarks) 的本地化字符串。 /// @@ -2895,6 +2949,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Override Outbound And Endpoint Config Only 的本地化字符串。 + /// + public static string TbSBCustomConfigDesc { + get { + return ResourceManager.GetString("TbSBCustomConfigDesc", resourceCulture); + } + } + /// /// 查找类似 Encryption method (security) 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index e772a8e6..7eca94da 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1464,4 +1464,25 @@ Custom DNS Enabled, This Page's Settings Invalid + + Please fill in the correct custom config + + + Custom Config + + + Enable Custom Config + + + v2ray Custom Config + + + sing-box Custom Config + + + Override Outbound Config, routing.balancers and routing.rules.outboundTag Only + + + Override Outbound And Endpoint Config Only + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index f161adcc..5f87950c 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1464,4 +1464,25 @@ Custom DNS Enabled, This Page's Settings Invalid + + Please fill in the correct custom config + + + Custom Config + + + Enable Custom Config + + + v2ray Custom Config + + + sing-box Custom Config + + + Override Outbound Config, routing.balancers and routing.rules.outboundTag Only + + + Override Outbound And Endpoint Config Only + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index ffb5b1cd..018a9d75 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1464,4 +1464,25 @@ Custom DNS Enabled, This Page's Settings Invalid + + Please fill in the correct custom config + + + Custom Config + + + Enable Custom Config + + + v2ray Custom Config + + + sing-box Custom Config + + + Override Outbound Config, routing.balancers and routing.rules.outboundTag Only + + + Override Outbound And Endpoint Config Only + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 63e31f51..9f03a63e 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1464,4 +1464,25 @@ Custom DNS Enabled, This Page's Settings Invalid + + Please fill in the correct custom config + + + Custom Config + + + Enable Custom Config + + + v2ray Custom Config + + + sing-box Custom Config + + + Override Outbound Config, routing.balancers and routing.rules.outboundTag Only + + + Override Outbound And Endpoint Config Only + \ 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 e8863c73..bef82b61 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1461,4 +1461,25 @@ 自定义 DNS 已启用,此页面配置将无效 + + 请填写正确的自定义配置 + + + 自定义配置 + + + 启用自定义配置 + + + v2ray 自定义配置 + + + sing-box 自定义配置 + + + 仅覆盖出站配置,routing.balancers 和 routing.rules.outboundTag + + + 仅覆盖出站和端点配置 + \ 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 4b7a44a5..a08f9791 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1461,4 +1461,25 @@ Custom DNS Enabled, This Page's Settings Invalid + + Please fill in the correct custom config + + + Custom Config + + + Enable Custom Config + + + v2ray Custom Config + + + sing-box Custom Config + + + Override Outbound Config, routing.balancers and routing.rules.outboundTag Only + + + Override Outbound And Endpoint Config Only + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs index 452e1976..4d730f17 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs @@ -4,6 +4,7 @@ using System.Net; using System.Net.NetworkInformation; using System.Reactive; using System.Text; +using System.Text.Json.Nodes; using DynamicData; using ServiceLib.Models; @@ -83,7 +84,51 @@ 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); + if (customConfig.Enabled && (!customConfig.Config.IsNullOrEmpty())) + { + //var customConfigObj = JsonUtils.Deserialize(customConfig.Config); + //if (customConfigObj != null) + //{ + // customConfigObj.outbounds = JsonUtils.DeepCopy(singboxConfig.outbounds); + // singboxConfig = customConfigObj; + //} + + // skip deserialize + var customConfigNode = JsonNode.Parse(customConfig.Config); + if (customConfigNode != null) + { + // customConfigNode["outbounds"] = JsonNode.Parse(JsonUtils.Serialize(singboxConfig.outbounds)); + // append outbounds instead of override + if (customConfigNode["outbounds"] is JsonArray customOutboundsNode) + { + if (JsonNode.Parse(JsonUtils.Serialize(singboxConfig.outbounds)) is JsonArray newOutbounds) + { + foreach (var outbound in newOutbounds) + { + customOutboundsNode.Add(outbound?.DeepClone()); + } + } + } + // endpoints + if (customConfigNode["endpoints"] is JsonArray customEndpointsNode) + { + if (singboxConfig.endpoints != null && JsonNode.Parse(JsonUtils.Serialize(singboxConfig.endpoints)) is JsonArray newEndpoints) + { + foreach (var endpoint in newEndpoints) + { + customEndpointsNode.Add(endpoint?.DeepClone()); + } + } + } + ret.Data = customConfigNode.ToJsonString(new() { WriteIndented = true }); + } + } + else + { + ret.Data = JsonUtils.Serialize(singboxConfig); + } return ret; } catch (Exception ex) @@ -439,7 +484,50 @@ public class CoreConfigSingboxService await ConvertGeo2Ruleset(singboxConfig); ret.Success = true; - ret.Data = JsonUtils.Serialize(singboxConfig); + + var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.sing_box); + if (customConfig.Enabled && (!customConfig.Config.IsNullOrEmpty())) + { + //var customConfigObj = JsonUtils.Deserialize(customConfig.Config); + //if (customConfigObj != null) + //{ + // customConfigObj.outbounds = JsonUtils.DeepCopy(singboxConfig.outbounds); + // singboxConfig = customConfigObj; + //} + // skip deserialize + var customConfigNode = JsonNode.Parse(customConfig.Config); + if (customConfigNode != null) + { + // customConfigNode["outbounds"] = JsonNode.Parse(JsonUtils.Serialize(singboxConfig.outbounds)); + // append outbounds instead of override + if (customConfigNode["outbounds"] is JsonArray customOutboundsNode) + { + if (JsonNode.Parse(JsonUtils.Serialize(singboxConfig.outbounds)) is JsonArray newOutbounds) + { + foreach (var outbound in newOutbounds) + { + customOutboundsNode.Add(outbound?.DeepClone()); + } + } + } + // endpoints + if (customConfigNode["endpoints"] is JsonArray customEndpointsNode) + { + if (singboxConfig.endpoints != null && JsonNode.Parse(JsonUtils.Serialize(singboxConfig.endpoints)) is JsonArray newEndpoints) + { + foreach (var endpoint in newEndpoints) + { + customEndpointsNode.Add(endpoint?.DeepClone()); + } + } + } + ret.Data = customConfigNode.ToJsonString(new() { WriteIndented = true }); + } + } + else + { + ret.Data = JsonUtils.Serialize(singboxConfig); + } return ret; } catch (Exception ex) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs index ec570b03..37374152 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs @@ -3,6 +3,7 @@ using System.Net.NetworkInformation; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; +using ServiceLib.Models; namespace ServiceLib.Services.CoreConfig; @@ -68,7 +69,39 @@ 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); + if (customConfig.Enabled && (!customConfig.Config.IsNullOrEmpty())) + { + //var customConfigObj = JsonUtils.Deserialize(customConfig.Config); + //if (customConfigObj != null) + //{ + // customConfigObj.outbounds = JsonUtils.DeepCopy(v2rayConfig.outbounds); + // v2rayConfig = customConfigObj; + //} + // spik deserialize + var customConfigNode = JsonNode.Parse(customConfig.Config); + if (customConfigNode != null) + { + //customConfigNode["outbounds"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.outbounds)); + // append outbounds instead of override + if (customConfigNode["outbounds"] is JsonArray customOutboundsNode) + { + if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.outbounds)) is JsonArray newOutbounds) + { + foreach (var outbound in newOutbounds) + { + customOutboundsNode.Add(outbound?.DeepClone()); + } + } + } + ret.Data = customConfigNode.ToJsonString(new() { WriteIndented = true }); + } + } + else + { + ret.Data = JsonUtils.Serialize(v2rayConfig); + } return ret; } catch (Exception ex) @@ -197,7 +230,82 @@ public class CoreConfigV2rayService } ret.Success = true; - ret.Data = JsonUtils.Serialize(v2rayConfig); + + var customConfig = await AppHandler.Instance.GetCustomConfigItem(ECoreType.Xray); + if (customConfig.Enabled && (!customConfig.Config.IsNullOrEmpty())) + { + //var customConfigObj = JsonUtils.Deserialize(customConfig.Config); + //if (customConfigObj != null) + //{ + // foreach (var rule in customConfigObj.routing.rules) + // { + // if (rule.outboundTag == Global.ProxyTag) + // { + // rule.outboundTag = null; + // rule.balancerTag = balancer.tag; + // } + // } + // customConfigObj.routing.balancers = JsonUtils.DeepCopy(v2rayConfig.routing.balancers); + // customConfigObj.outbounds = JsonUtils.DeepCopy(v2rayConfig.outbounds); + // v2rayConfig = customConfigObj; + //} + // skip deserialize + var customConfigNode = JsonNode.Parse(customConfig.Config); + if (customConfigNode != null) + { + var rulesNode = customConfigNode["routing"]?["rules"]; + if (rulesNode != null) + { + foreach (var rule in rulesNode.AsArray()) + { + if (rule["outboundTag"]?.GetValue() == Global.ProxyTag) + { + rule["outboundTag"] = null; + 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)); + } + + // append outbounds instead of override + if (customConfigNode["outbounds"] is JsonArray customOutboundsNode) + { + if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.outbounds)) is JsonArray newOutbounds) + { + foreach (var outbound in newOutbounds) + { + customOutboundsNode.Add(outbound?.DeepClone()); + } + } + } + ret.Data = customConfigNode.ToJsonString(new() { WriteIndented = true }); + } + } + else + { + ret.Data = JsonUtils.Serialize(v2rayConfig); + } return ret; } catch (Exception ex) diff --git a/v2rayN/ServiceLib/ViewModels/CustomConfigViewModel.cs b/v2rayN/ServiceLib/ViewModels/CustomConfigViewModel.cs new file mode 100644 index 00000000..74527d24 --- /dev/null +++ b/v2rayN/ServiceLib/ViewModels/CustomConfigViewModel.cs @@ -0,0 +1,106 @@ +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; } + + 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; + + var item2 = await AppHandler.Instance.GetCustomConfigItem(ECoreType.sing_box); + EnableCustomConfig4Singbox = item2?.Enabled ?? false; + CustomConfig4Singbox = item2?.Config ?? string.Empty; + CustomTunConfig4Singbox = item2?.TunConfig ?? 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; + + if (CustomConfig4Ray.IsNotEmpty()) + { + item.Config = CustomConfig4Ray; + } + + 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; + + var hasChanges = false; + + if (CustomConfig4Singbox.IsNotEmpty()) + { + item.Config = CustomConfig4Singbox; + hasChanges = true; + } + + if (CustomTunConfig4Singbox.IsNotEmpty()) + { + item.TunConfig = CustomTunConfig4Singbox; + hasChanges = true; + } + + if (hasChanges) + { + 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..80218aa8 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/CustomConfigWindow.axaml @@ -0,0 +1,144 @@ + + + +