diff --git a/.editorconfig b/.editorconfig index 475813fa..7213a91c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -97,3 +97,7 @@ resharper_suggest_var_or_type_simple_types_highlighting = hint resharper_web_config_module_not_resolved_highlighting = warning resharper_web_config_type_not_resolved_highlighting = warning resharper_web_config_wrong_module_highlighting = warning + +[*.csproj] +indent_size = 2 +ij_xml_space_inside_empty_tag = true \ No newline at end of file diff --git a/Netch/Constants.cs b/Netch/Constants.cs index 5ec78658..4323f845 100644 --- a/Netch/Constants.cs +++ b/Netch/Constants.cs @@ -1,31 +1,30 @@ -namespace Netch +namespace Netch; + +public static class Constants { - public static class Constants + public const string TempConfig = "data\\last.json"; + public const string TempRouteFile = "data\\route.txt"; + + public const string AioDnsRuleFile = "bin\\aiodns.conf"; + public const string NFDriver = "bin\\nfdriver.sys"; + public const string STUNServersFile = "bin\\stun.txt"; + + public const string LogFile = "logging\\application.log"; + + public const string OutputTemplate = @"[{Timestamp:yyyy-MM-dd HH:mm:ss}][{Level}] {Message:lj}{NewLine}{Exception}"; + public const string EOF = "\r\n"; + + public const string DefaultGroup = "NONE"; + + public static class Parameter { - public const string TempConfig = "data\\last.json"; - public const string TempRouteFile = "data\\route.txt"; - - public const string AioDnsRuleFile = "bin\\aiodns.conf"; - public const string NFDriver = "bin\\nfdriver.sys"; - public const string STUNServersFile = "bin\\stun.txt"; - - public const string LogFile = "logging\\application.log"; - - public const string OutputTemplate = @"[{Timestamp:yyyy-MM-dd HH:mm:ss}][{Level}] {Message:lj}{NewLine}{Exception}"; - public const string EOF = "\r\n"; - - public const string DefaultGroup = "NONE"; - - public static class Parameter - { - public const string Show = "-show"; - public const string ForceUpdate = "-forceUpdate"; - } - - public const string WintunDllFile = "bin\\wintun.dll"; - public const string DisableModeDirectoryFileName = "disabled"; - - public const string DefaultPrimaryDNS = "1.1.1.1:53"; - public const string DefaultCNPrimaryDNS = "223.5.5.5:53"; + public const string Show = "-show"; + public const string ForceUpdate = "-forceUpdate"; } + + public const string WintunDllFile = "bin\\wintun.dll"; + public const string DisableModeDirectoryFileName = "disabled"; + + public const string DefaultPrimaryDNS = "1.1.1.1:53"; + public const string DefaultCNPrimaryDNS = "223.5.5.5:53"; } \ No newline at end of file diff --git a/Netch/Controllers/DNSController.cs b/Netch/Controllers/DNSController.cs index d98a593f..5aee6a3f 100644 --- a/Netch/Controllers/DNSController.cs +++ b/Netch/Controllers/DNSController.cs @@ -1,33 +1,30 @@ -using System.IO; -using System.Threading.Tasks; -using Netch.Interfaces; +using Netch.Interfaces; using Netch.Models; using static Netch.Interops.AioDNS; -namespace Netch.Controllers +namespace Netch.Controllers; + +public class DNSController : IController { - public class DNSController : IController + public string Name => "DNS Service"; + + public async Task StartAsync(string listenAddress) { - public string Name => "DNS Service"; + var aioDnsConfig = Global.Settings.AioDNS; - public async Task StartAsync(string listenAddress) - { - var aioDnsConfig = Global.Settings.AioDNS; + Dial(NameList.TYPE_REST, ""); + Dial(NameList.TYPE_LIST, Path.GetFullPath(Constants.AioDnsRuleFile)); + // TODO remove ListenPort setting + Dial(NameList.TYPE_LISN, $"{listenAddress}:{aioDnsConfig.ListenPort}"); + Dial(NameList.TYPE_CDNS, $"{aioDnsConfig.ChinaDNS}"); + Dial(NameList.TYPE_ODNS, $"{aioDnsConfig.OtherDNS}"); - Dial(NameList.TYPE_REST, ""); - Dial(NameList.TYPE_LIST, Path.GetFullPath(Constants.AioDnsRuleFile)); - // TODO remove ListenPort setting - Dial(NameList.TYPE_LISN, $"{listenAddress}:{aioDnsConfig.ListenPort}"); - Dial(NameList.TYPE_CDNS, $"{aioDnsConfig.ChinaDNS}"); - Dial(NameList.TYPE_ODNS, $"{aioDnsConfig.OtherDNS}"); + if (!await InitAsync()) + throw new MessageException("AioDNS start failed."); + } - if (!await InitAsync()) - throw new MessageException("AioDNS start failed."); - } - - public async Task StopAsync() - { - await FreeAsync(); - } + public async Task StopAsync() + { + await FreeAsync(); } } \ No newline at end of file diff --git a/Netch/Controllers/Guard.cs b/Netch/Controllers/Guard.cs index 0f369210..c0d78c94 100644 --- a/Netch/Controllers/Guard.cs +++ b/Netch/Controllers/Guard.cs @@ -1,182 +1,176 @@ -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Text; -using System.Threading.Tasks; using Microsoft.VisualStudio.Threading; using Netch.Enums; using Netch.Models; using Netch.Utils; -using Serilog; -namespace Netch.Controllers +namespace Netch.Controllers; + +public abstract class Guard { - public abstract class Guard + private FileStream? _logFileStream; + private StreamWriter? _logStreamWriter; + + /// application path relative of Netch\bin + /// + /// application output encode + protected Guard(string mainFile, bool redirectOutput = true, Encoding? encoding = null) { - private FileStream? _logFileStream; - private StreamWriter? _logStreamWriter; + RedirectOutput = redirectOutput; - /// application path relative of Netch\bin - /// - /// application output encode - protected Guard(string mainFile, bool redirectOutput = true, Encoding? encoding = null) + var fileName = Path.GetFullPath($"bin\\{mainFile}"); + + if (!File.Exists(fileName)) + throw new MessageException(i18N.Translate($"bin\\{mainFile} file not found!")); + + Instance = new Process { - RedirectOutput = redirectOutput; - - var fileName = Path.GetFullPath($"bin\\{mainFile}"); - - if (!File.Exists(fileName)) - throw new MessageException(i18N.Translate($"bin\\{mainFile} file not found!")); - - Instance = new Process + StartInfo = { - StartInfo = - { - FileName = fileName, - WorkingDirectory = $"{Global.NetchDir}\\bin", - CreateNoWindow = true, - UseShellExecute = !RedirectOutput, - RedirectStandardOutput = RedirectOutput, - StandardOutputEncoding = RedirectOutput ? encoding : null, - RedirectStandardError = RedirectOutput, - StandardErrorEncoding = RedirectOutput ? encoding : null, - WindowStyle = ProcessWindowStyle.Hidden - } - }; - } - - protected string LogPath => Path.Combine(Global.NetchDir, $"logging\\{Name}.log"); - - protected virtual IEnumerable StartedKeywords { get; } = new List(); - - protected virtual IEnumerable FailedKeywords { get; } = new List(); - - public abstract string Name { get; } - - private State State { get; set; } = State.Waiting; - - private bool RedirectOutput { get; } - - public Process Instance { get; } - - ~Guard() - { - _logFileStream?.Dispose(); - _logStreamWriter?.Dispose(); - Instance.Dispose(); - } - - protected async Task StartGuardAsync(string argument, ProcessPriorityClass priority = ProcessPriorityClass.Normal) - { - State = State.Starting; - - _logFileStream = File.Open(LogPath, FileMode.Create, FileAccess.Write, FileShare.Read); - _logStreamWriter = new StreamWriter(_logFileStream) { AutoFlush = true }; - - Instance.StartInfo.Arguments = argument; - Instance.Start(); - Global.Job.AddProcess(Instance); - - if (priority != ProcessPriorityClass.Normal) - Instance.PriorityClass = priority; - - if (RedirectOutput) - { - Task.Run(() => ReadOutput(Instance.StandardOutput)).Forget(); - Task.Run(() => ReadOutput(Instance.StandardError)).Forget(); - - if (!StartedKeywords.Any()) - { - // Skip, No started keyword - State = State.Started; - return; - } - - // wait ReadOutput change State - for (var i = 0; i < 1000; i++) - { - await Task.Delay(50); - switch (State) - { - case State.Started: - OnStarted(); - return; - case State.Stopped: - await StopGuardAsync(); - OnStartFailed(); - throw new MessageException($"{Name} 控制器启动失败"); - } - } - - await StopGuardAsync(); - throw new MessageException($"{Name} 控制器启动超时"); + FileName = fileName, + WorkingDirectory = $"{Global.NetchDir}\\bin", + CreateNoWindow = true, + UseShellExecute = !RedirectOutput, + RedirectStandardOutput = RedirectOutput, + StandardOutputEncoding = RedirectOutput ? encoding : null, + RedirectStandardError = RedirectOutput, + StandardErrorEncoding = RedirectOutput ? encoding : null, + WindowStyle = ProcessWindowStyle.Hidden } - } + }; + } - private void ReadOutput(TextReader reader) + protected string LogPath => Path.Combine(Global.NetchDir, $"logging\\{Name}.log"); + + protected virtual IEnumerable StartedKeywords { get; } = new List(); + + protected virtual IEnumerable FailedKeywords { get; } = new List(); + + public abstract string Name { get; } + + private State State { get; set; } = State.Waiting; + + private bool RedirectOutput { get; } + + public Process Instance { get; } + + ~Guard() + { + _logFileStream?.Dispose(); + _logStreamWriter?.Dispose(); + Instance.Dispose(); + } + + protected async Task StartGuardAsync(string argument, ProcessPriorityClass priority = ProcessPriorityClass.Normal) + { + State = State.Starting; + + _logFileStream = File.Open(LogPath, FileMode.Create, FileAccess.Write, FileShare.Read); + _logStreamWriter = new StreamWriter(_logFileStream) { AutoFlush = true }; + + Instance.StartInfo.Arguments = argument; + Instance.Start(); + Global.Job.AddProcess(Instance); + + if (priority != ProcessPriorityClass.Normal) + Instance.PriorityClass = priority; + + if (RedirectOutput) { - string? line; - while ((line = reader.ReadLine()) != null) - { - _logStreamWriter!.WriteLine(line); - OnReadNewLine(line); + Task.Run(() => ReadOutput(Instance.StandardOutput)).Forget(); + Task.Run(() => ReadOutput(Instance.StandardError)).Forget(); - if (State == State.Starting) + if (!StartedKeywords.Any()) + { + // Skip, No started keyword + State = State.Started; + return; + } + + // wait ReadOutput change State + for (var i = 0; i < 1000; i++) + { + await Task.Delay(50); + switch (State) { - if (StartedKeywords.Any(s => line.Contains(s))) - State = State.Started; - else if (FailedKeywords.Any(s => line.Contains(s))) - { + case State.Started: + OnStarted(); + return; + case State.Stopped: + await StopGuardAsync(); OnStartFailed(); - State = State.Stopped; - } + throw new MessageException($"{Name} 控制器启动失败"); } } - State = State.Stopped; - } - - public virtual async Task StopAsync() - { await StopGuardAsync(); - } - - protected async Task StopGuardAsync() - { - _logStreamWriter?.Close(); - _logFileStream?.Close(); - - try - { - if (Instance is { HasExited: false }) - { - Instance.Kill(); - await Instance.WaitForExitAsync(); - } - } - catch (Win32Exception e) - { - Log.Error(e, "Stop {Name} failed", Instance.ProcessName); - } - catch - { - // ignored - } - } - - protected virtual void OnStarted() - { - } - - protected virtual void OnReadNewLine(string line) - { - } - - protected virtual void OnStartFailed() - { - Utils.Utils.Open(LogPath); + throw new MessageException($"{Name} 控制器启动超时"); } } + + private void ReadOutput(TextReader reader) + { + string? line; + while ((line = reader.ReadLine()) != null) + { + _logStreamWriter!.WriteLine(line); + OnReadNewLine(line); + + if (State == State.Starting) + { + if (StartedKeywords.Any(s => line.Contains(s))) + State = State.Started; + else if (FailedKeywords.Any(s => line.Contains(s))) + { + OnStartFailed(); + State = State.Stopped; + } + } + } + + State = State.Stopped; + } + + public virtual async Task StopAsync() + { + await StopGuardAsync(); + } + + protected async Task StopGuardAsync() + { + _logStreamWriter?.Close(); + _logFileStream?.Close(); + + try + { + if (Instance is { HasExited: false }) + { + Instance.Kill(); + await Instance.WaitForExitAsync(); + } + } + catch (Win32Exception e) + { + Log.Error(e, "Stop {Name} failed", Instance.ProcessName); + } + catch + { + // ignored + } + } + + protected virtual void OnStarted() + { + } + + protected virtual void OnReadNewLine(string line) + { + } + + protected virtual void OnStartFailed() + { + Utils.Utils.Open(LogPath); + } } \ No newline at end of file diff --git a/Netch/Controllers/MainController.cs b/Netch/Controllers/MainController.cs index e904bf8d..dd66e14e 100644 --- a/Netch/Controllers/MainController.cs +++ b/Netch/Controllers/MainController.cs @@ -1,8 +1,4 @@ -using System; using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; using Microsoft.VisualStudio.Threading; using Netch.Interfaces; using Netch.Models; @@ -10,193 +6,191 @@ using Netch.Models.Modes; using Netch.Servers; using Netch.Services; using Netch.Utils; -using Serilog; -namespace Netch.Controllers +namespace Netch.Controllers; + +public static class MainController { - public static class MainController + public static Socks5Server? Socks5Server { get; private set; } + + public static Server? Server { get; private set; } + + public static Mode? Mode { get; private set; } + + public static IServerController? ServerController { get; private set; } + + public static IModeController? ModeController { get; private set; } + + private static readonly AsyncSemaphore Lock = new(1); + + public static async Task StartAsync(Server server, Mode mode) { - public static Socks5Server? Socks5Server { get; private set; } + using var releaser = await Lock.EnterAsync(); - public static Server? Server { get; private set; } + Log.Information("Start MainController: {Server} {Mode}", $"{server.Type}", $"[{(int)mode.Type}]{mode.i18NRemark}"); - public static Mode? Mode { get; private set; } + if (await DnsUtils.LookupAsync(server.Hostname) == null) + throw new MessageException(i18N.Translate("Lookup Server hostname failed")); - public static IServerController? ServerController { get; private set; } + // TODO Disable NAT Type Test setting + // cache STUN Server ip to prevent "Wrong STUN Server" + DnsUtils.LookupAsync(Global.Settings.STUN_Server).Forget(); - public static IModeController? ModeController { get; private set; } + Server = server; + Mode = mode; - private static readonly AsyncSemaphore Lock = new(1); + await Task.WhenAll(Task.Run(NativeMethods.RefreshDNSCache), Task.Run(Firewall.AddNetchFwRules)); - public static async Task StartAsync(Server server, Mode mode) + try { - using var releaser = await Lock.EnterAsync(); + ModeController = ModeService.GetModeControllerByType(mode.Type, out var modePort, out var portName); - Log.Information("Start MainController: {Server} {Mode}", $"{server.Type}", $"[{(int)mode.Type}]{mode.i18NRemark}"); + if (modePort != null) + TryReleaseTcpPort((ushort)modePort, portName); - if (await DnsUtils.LookupAsync(server.Hostname) == null) - throw new MessageException(i18N.Translate("Lookup Server hostname failed")); - - // TODO Disable NAT Type Test setting - // cache STUN Server ip to prevent "Wrong STUN Server" - DnsUtils.LookupAsync(Global.Settings.STUN_Server).Forget(); - - Server = server; - Mode = mode; - - await Task.WhenAll(Task.Run(NativeMethods.RefreshDNSCache), Task.Run(Firewall.AddNetchFwRules)); - - try + if (Server is Socks5Server socks5 && (!socks5.Auth() || ModeController.Features.HasFlag(ModeFeature.SupportSocks5Auth))) { - ModeController = ModeService.GetModeControllerByType(mode.Type, out var modePort, out var portName); - - if (modePort != null) - TryReleaseTcpPort((ushort)modePort, portName); - - if (Server is Socks5Server socks5 && (!socks5.Auth() || ModeController.Features.HasFlag(ModeFeature.SupportSocks5Auth))) - { - Socks5Server = socks5; - } - else - { - // Start Server Controller to get a local socks5 server - Log.Debug("Server Information: {Data}", $"{server.Type} {server.MaskedData()}"); - - ServerController = ServerHelper.GetUtilByTypeName(server.Type).GetController(); - Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ServerController.Name)); - - TryReleaseTcpPort(ServerController.Socks5LocalPort(), "Socks5"); - Socks5Server = await ServerController.StartAsync(server); - - StatusPortInfoText.Socks5Port = Socks5Server.Port; - StatusPortInfoText.UpdateShareLan(); - } - - // Start Mode Controller - Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ModeController.Name)); - - await ModeController.StartAsync(Socks5Server, mode); + Socks5Server = socks5; } - catch (Exception e) + else { - releaser.Dispose(); - await StopAsync(); + // Start Server Controller to get a local socks5 server + Log.Debug("Server Information: {Data}", $"{server.Type} {server.MaskedData()}"); - switch (e) - { - case DllNotFoundException: - case FileNotFoundException: - throw new Exception(e.Message + "\n\n" + i18N.Translate("Missing File or runtime components")); - case MessageException: - throw; - default: - Log.Error(e, "Unhandled Exception When Start MainController"); - Utils.Utils.Open(Constants.LogFile); - throw new MessageException($"{i18N.Translate("Unhandled Exception")}\n{e.Message}"); - } + ServerController = ServerHelper.GetUtilByTypeName(server.Type).GetController(); + Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ServerController.Name)); + + TryReleaseTcpPort(ServerController.Socks5LocalPort(), "Socks5"); + Socks5Server = await ServerController.StartAsync(server); + + StatusPortInfoText.Socks5Port = Socks5Server.Port; + StatusPortInfoText.UpdateShareLan(); } + + // Start Mode Controller + Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ModeController.Name)); + + await ModeController.StartAsync(Socks5Server, mode); } - - public static async Task StopAsync() + catch (Exception e) { - if (Lock.CurrentCount == 0) - { - (await Lock.EnterAsync()).Dispose(); - if (ServerController == null && ModeController == null) - // stopped - return; + releaser.Dispose(); + await StopAsync(); - // else begin stop + switch (e) + { + case DllNotFoundException: + case FileNotFoundException: + throw new Exception(e.Message + "\n\n" + i18N.Translate("Missing File or runtime components")); + case MessageException: + throw; + default: + Log.Error(e, "Unhandled Exception When Start MainController"); + Utils.Utils.Open(Constants.LogFile); + throw new MessageException($"{i18N.Translate("Unhandled Exception")}\n{e.Message}"); } - - using var _ = await Lock.EnterAsync(); - - if (ServerController == null && ModeController == null) - return; - - Log.Information("Stop Main Controller"); - StatusPortInfoText.Reset(); - - var tasks = new[] - { - ServerController?.StopAsync() ?? Task.CompletedTask, - ModeController?.StopAsync() ?? Task.CompletedTask - }; - - try - { - await Task.WhenAll(tasks); - } - catch (Exception e) - { - Log.Error(e, "MainController Stop Error"); - } - - ServerController = null; - ModeController = null; - } - - public static void PortCheck(ushort port, string portName, PortType portType = PortType.Both) - { - try - { - PortHelper.CheckPort(port, portType); - } - catch (PortInUseException) - { - throw new MessageException(i18N.TranslateFormat("The {0} port is in use.", $"{portName} ({port})")); - } - catch (PortReservedException) - { - throw new MessageException(i18N.TranslateFormat("The {0} port is reserved by system.", $"{portName} ({port})")); - } - } - - public static void TryReleaseTcpPort(ushort port, string portName) - { - foreach (var p in PortHelper.GetProcessByUsedTcpPort(port)) - { - var fileName = p.MainModule?.FileName; - if (fileName == null) - continue; - - if (fileName.StartsWith(Global.NetchDir)) - { - p.Kill(); - p.WaitForExit(); - } - else - { - throw new MessageException(i18N.TranslateFormat("The {0} port is used by {1}.", $"{portName} ({port})", $"({p.Id}){fileName}")); - } - } - - PortCheck(port, portName, PortType.TCP); - } - - public static async Task DiscoveryNatTypeAsync(CancellationToken ctx = default) - { - Debug.Assert(Socks5Server != null, nameof(Socks5Server) + " != null"); - return await Socks5ServerTestUtils.DiscoveryNatTypeAsync(Socks5Server, ctx); - } - - public static async Task HttpConnectAsync(CancellationToken ctx = default) - { - Debug.Assert(Socks5Server != null, nameof(Socks5Server) + " != null"); - try - { - return await Socks5ServerTestUtils.HttpConnectAsync(Socks5Server, ctx); - } - catch (OperationCanceledException) - { - // ignored - } - catch (Exception e) - { - Log.Warning(e, "Unhandled Socks5ServerTestUtils.HttpConnectAsync Exception"); - } - - return null; } } + + public static async Task StopAsync() + { + if (Lock.CurrentCount == 0) + { + (await Lock.EnterAsync()).Dispose(); + if (ServerController == null && ModeController == null) + // stopped + return; + + // else begin stop + } + + using var _ = await Lock.EnterAsync(); + + if (ServerController == null && ModeController == null) + return; + + Log.Information("Stop Main Controller"); + StatusPortInfoText.Reset(); + + var tasks = new[] + { + ServerController?.StopAsync() ?? Task.CompletedTask, + ModeController?.StopAsync() ?? Task.CompletedTask + }; + + try + { + await Task.WhenAll(tasks); + } + catch (Exception e) + { + Log.Error(e, "MainController Stop Error"); + } + + ServerController = null; + ModeController = null; + } + + public static void PortCheck(ushort port, string portName, PortType portType = PortType.Both) + { + try + { + PortHelper.CheckPort(port, portType); + } + catch (PortInUseException) + { + throw new MessageException(i18N.TranslateFormat("The {0} port is in use.", $"{portName} ({port})")); + } + catch (PortReservedException) + { + throw new MessageException(i18N.TranslateFormat("The {0} port is reserved by system.", $"{portName} ({port})")); + } + } + + public static void TryReleaseTcpPort(ushort port, string portName) + { + foreach (var p in PortHelper.GetProcessByUsedTcpPort(port)) + { + var fileName = p.MainModule?.FileName; + if (fileName == null) + continue; + + if (fileName.StartsWith(Global.NetchDir)) + { + p.Kill(); + p.WaitForExit(); + } + else + { + throw new MessageException(i18N.TranslateFormat("The {0} port is used by {1}.", $"{portName} ({port})", $"({p.Id}){fileName}")); + } + } + + PortCheck(port, portName, PortType.TCP); + } + + public static async Task DiscoveryNatTypeAsync(CancellationToken ctx = default) + { + Debug.Assert(Socks5Server != null, nameof(Socks5Server) + " != null"); + return await Socks5ServerTestUtils.DiscoveryNatTypeAsync(Socks5Server, ctx); + } + + public static async Task HttpConnectAsync(CancellationToken ctx = default) + { + Debug.Assert(Socks5Server != null, nameof(Socks5Server) + " != null"); + try + { + return await Socks5ServerTestUtils.HttpConnectAsync(Socks5Server, ctx); + } + catch (OperationCanceledException) + { + // ignored + } + catch (Exception e) + { + Log.Warning(e, "Unhandled Socks5ServerTestUtils.HttpConnectAsync Exception"); + } + + return null; + } } \ No newline at end of file diff --git a/Netch/Controllers/NFController.cs b/Netch/Controllers/NFController.cs index 82a02e6c..18399095 100644 --- a/Netch/Controllers/NFController.cs +++ b/Netch/Controllers/NFController.cs @@ -1,262 +1,255 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; +using System.Net; using System.ServiceProcess; -using System.Threading.Tasks; using Netch.Interfaces; using Netch.Models; using Netch.Models.Modes; using Netch.Models.Modes.ProcessMode; using Netch.Servers; using Netch.Utils; -using Serilog; using static Netch.Interops.Redirector; -namespace Netch.Controllers +namespace Netch.Controllers; + +public class NFController : IModeController { - public class NFController : IModeController + private Server? _server; + private Redirector _mode = null!; + private RedirectorConfig _rdrConfig = null!; + + private static readonly ServiceController NFService = new("netfilter2"); + + private static readonly string SystemDriver = $"{Environment.SystemDirectory}\\drivers\\netfilter2.sys"; + + public string Name => "Redirector"; + + public ModeFeature Features => ModeFeature.SupportIPv6 | ModeFeature.SupportSocks5Auth; + + public async Task StartAsync(Socks5Server server, Mode mode) { - private Server? _server; - private Redirector _mode = null!; - private RedirectorConfig _rdrConfig = null!; + if (mode is not Redirector processMode) + throw new InvalidOperationException(); - private static readonly ServiceController NFService = new("netfilter2"); + _server = server; + _mode = processMode; + _rdrConfig = Global.Settings.Redirector; - private static readonly string SystemDriver = $"{Environment.SystemDirectory}\\drivers\\netfilter2.sys"; + CheckDriver(); - public string Name => "Redirector"; + Dial(NameList.AIO_FILTERLOOPBACK, _mode.FilterLoopback); + Dial(NameList.AIO_FILTERINTRANET, _mode.FilterIntranet); + Dial(NameList.AIO_FILTERPARENT, _mode.FilterParent ?? _rdrConfig.HandleOnlyDNS); + Dial(NameList.AIO_FILTERICMP, _mode.FilterICMP ?? _rdrConfig.FilterICMP); + if (_mode.FilterICMP ?? _rdrConfig.FilterICMP) + Dial(NameList.AIO_ICMPING, (_mode.FilterICMP != null ? _mode.ICMPDelay ?? 10 : _rdrConfig.ICMPDelay).ToString()); - public ModeFeature Features => ModeFeature.SupportIPv6 | ModeFeature.SupportSocks5Auth; + Dial(NameList.AIO_FILTERTCP, _mode.FilterTCP ?? _rdrConfig.FilterTCP); + Dial(NameList.AIO_FILTERUDP, _mode.FilterUDP ?? _rdrConfig.FilterUDP); - public async Task StartAsync(Socks5Server server, Mode mode) + // DNS + Dial(NameList.AIO_FILTERDNS, _mode.FilterDNS ?? _rdrConfig.FilterDNS); + Dial(NameList.AIO_DNSONLY, _mode.HandleOnlyDNS ?? _rdrConfig.HandleOnlyDNS); + Dial(NameList.AIO_DNSPROX, _mode.DNSProxy ?? _rdrConfig.DNSProxy); + if (_mode.FilterDNS ?? _rdrConfig.FilterDNS) { - if (mode is not Redirector processMode) - throw new InvalidOperationException(); + var dnsStr = _mode.FilterDNS != null ? _mode.DNSHost : _rdrConfig.DNSHost; - _server = server; - _mode = processMode; - _rdrConfig = Global.Settings.Redirector; + dnsStr = dnsStr.ValueOrDefault() ?? $"{Constants.DefaultPrimaryDNS}:53"; - CheckDriver(); + var dns = IPEndPoint.Parse(dnsStr); + if (dns.Port == 0) + dns.Port = 53; - Dial(NameList.AIO_FILTERLOOPBACK, _mode.FilterLoopback); - Dial(NameList.AIO_FILTERINTRANET, _mode.FilterIntranet); - Dial(NameList.AIO_FILTERPARENT, _mode.FilterParent ?? _rdrConfig.HandleOnlyDNS); - Dial(NameList.AIO_FILTERICMP, _mode.FilterICMP ?? _rdrConfig.FilterICMP); - if (_mode.FilterICMP ?? _rdrConfig.FilterICMP) - Dial(NameList.AIO_ICMPING, (_mode.FilterICMP != null ? _mode.ICMPDelay ?? 10 : _rdrConfig.ICMPDelay).ToString()); - - Dial(NameList.AIO_FILTERTCP, _mode.FilterTCP ?? _rdrConfig.FilterTCP); - Dial(NameList.AIO_FILTERUDP, _mode.FilterUDP ?? _rdrConfig.FilterUDP); - - // DNS - Dial(NameList.AIO_FILTERDNS, _mode.FilterDNS ?? _rdrConfig.FilterDNS); - Dial(NameList.AIO_DNSONLY, _mode.HandleOnlyDNS ?? _rdrConfig.HandleOnlyDNS); - Dial(NameList.AIO_DNSPROX, _mode.DNSProxy ?? _rdrConfig.DNSProxy); - if (_mode.FilterDNS ?? _rdrConfig.FilterDNS) - { - var dnsStr = _mode.FilterDNS != null ? _mode.DNSHost : _rdrConfig.DNSHost; - - dnsStr = dnsStr.ValueOrDefault() ?? $"{Constants.DefaultPrimaryDNS}:53"; - - var dns = IPEndPoint.Parse(dnsStr); - if (dns.Port == 0) - dns.Port = 53; - - Dial(NameList.AIO_DNSHOST, dns.Address.ToString()); - Dial(NameList.AIO_DNSPORT, dns.Port.ToString()); - } - - // Server - Dial(NameList.AIO_TGTHOST, await server.AutoResolveHostnameAsync()); - Dial(NameList.AIO_TGTPORT, server.Port.ToString()); - Dial(NameList.AIO_TGTUSER, server.Username ?? string.Empty); - Dial(NameList.AIO_TGTPASS, server.Password ?? string.Empty); - - // Mode Rule - DialRule(); - - if (!await InitAsync()) - throw new MessageException("Redirector start failed."); + Dial(NameList.AIO_DNSHOST, dns.Address.ToString()); + Dial(NameList.AIO_DNSPORT, dns.Port.ToString()); } - public async Task StopAsync() - { - await FreeAsync(); - } + // Server + Dial(NameList.AIO_TGTHOST, await server.AutoResolveHostnameAsync()); + Dial(NameList.AIO_TGTPORT, server.Port.ToString()); + Dial(NameList.AIO_TGTUSER, server.Username ?? string.Empty); + Dial(NameList.AIO_TGTPASS, server.Password ?? string.Empty); - #region CheckRule + // Mode Rule + DialRule(); - /// - /// - /// - /// - /// No Problem true - private static bool CheckCppRegex(string r, bool clear = true) - { - try - { - if (r.StartsWith("!")) - return Dial(NameList.AIO_ADDNAME, r.Substring(1)); - - return Dial(NameList.AIO_ADDNAME, r); - } - finally - { - if (clear) - Dial(NameList.AIO_CLRNAME, ""); - } - } - - /// - /// - /// - /// - /// No Problem true - public static bool CheckRules(IEnumerable rules, out IEnumerable results) - { - results = rules.Where(r => !CheckCppRegex(r, false)); - Dial(NameList.AIO_CLRNAME, ""); - return !results.Any(); - } - - public static string GenerateInvalidRulesMessage(IEnumerable rules) - { - return $"{string.Join("\n", rules)}\n" + i18N.Translate("Above rules does not conform to C++ regular expression syntax"); - } - - #endregion - - private void DialRule() - { - Dial(NameList.AIO_CLRNAME, ""); - var invalidList = new List(); - foreach (var s in _mode.Bypass) - { - if (!Dial(NameList.AIO_BYPNAME, s)) - invalidList.Add(s); - } - - foreach (var s in _mode.Handle) - { - if (!Dial(NameList.AIO_ADDNAME, s)) - invalidList.Add(s); - } - - if (invalidList.Any()) - throw new MessageException(GenerateInvalidRulesMessage(invalidList)); - - // Bypass Self - Dial(NameList.AIO_BYPNAME, "^" + Global.NetchDir.ToRegexString()); - } - - #region DriverUtil - - private static void CheckDriver() - { - var binFileVersion = Utils.Utils.GetFileVersion(Constants.NFDriver); - var systemFileVersion = Utils.Utils.GetFileVersion(SystemDriver); - - Log.Information("Built-in netfilter2 driver version: {Name}", binFileVersion); - Log.Information("Installed netfilter2 driver version: {Name}", systemFileVersion); - - if (!File.Exists(SystemDriver)) - { - // Install - InstallDriver(); - return; - } - - var reinstall = false; - if (Version.TryParse(binFileVersion, out var binResult) && Version.TryParse(systemFileVersion, out var systemResult)) - { - if (binResult.CompareTo(systemResult) > 0) - // Update - reinstall = true; - else if (systemResult.Major != binResult.Major) - // Downgrade when Major version different (may have breaking changes) - reinstall = true; - } - else - { - // Parse File versionName to Version failed - if (!systemFileVersion.Equals(binFileVersion)) - // versionNames are different, Reinstall - reinstall = true; - } - - if (!reinstall) - return; - - Log.Information("Update netfilter2 driver"); - UninstallDriver(); - InstallDriver(); - } - - /// - /// 安装 NF 驱动 - /// - /// 驱动是否安装成功 - private static void InstallDriver() - { - Log.Information("Install netfilter2 driver"); - Global.MainForm.StatusText(i18N.Translate("Installing netfilter2 driver")); - - if (!File.Exists(Constants.NFDriver)) - throw new MessageException(i18N.Translate("builtin driver files missing, can't install NF driver")); - - try - { - File.Copy(Constants.NFDriver, SystemDriver); - } - catch (Exception e) - { - Log.Error(e, "Copy netfilter2.sys failed\n"); - throw new MessageException($"Copy netfilter2.sys failed\n{e.Message}"); - } - - // 注册驱动文件 - if (Interops.Redirector.aio_register("netfilter2")) - { - Log.Information("Install netfilter2 driver finished"); - } - else - { - Log.Error("Register netfilter2 failed"); - } - } - - /// - /// 卸载 NF 驱动 - /// - /// 是否成功卸载 - public static bool UninstallDriver() - { - Log.Information("Uninstall netfilter2"); - try - { - if (NFService.Status == ServiceControllerStatus.Running) - { - NFService.Stop(); - NFService.WaitForStatus(ServiceControllerStatus.Stopped); - } - } - catch (Exception) - { - // ignored - } - - if (!File.Exists(SystemDriver)) - return true; - - Interops.Redirector.aio_unregister("netfilter2"); - File.Delete(SystemDriver); - - return true; - } - - #endregion + if (!await InitAsync()) + throw new MessageException("Redirector start failed."); } + + public async Task StopAsync() + { + await FreeAsync(); + } + + #region CheckRule + + /// + /// + /// + /// + /// No Problem true + private static bool CheckCppRegex(string r, bool clear = true) + { + try + { + if (r.StartsWith("!")) + return Dial(NameList.AIO_ADDNAME, r.Substring(1)); + + return Dial(NameList.AIO_ADDNAME, r); + } + finally + { + if (clear) + Dial(NameList.AIO_CLRNAME, ""); + } + } + + /// + /// + /// + /// + /// No Problem true + public static bool CheckRules(IEnumerable rules, out IEnumerable results) + { + results = rules.Where(r => !CheckCppRegex(r, false)); + Dial(NameList.AIO_CLRNAME, ""); + return !results.Any(); + } + + public static string GenerateInvalidRulesMessage(IEnumerable rules) + { + return $"{string.Join("\n", rules)}\n" + i18N.Translate("Above rules does not conform to C++ regular expression syntax"); + } + + #endregion + + private void DialRule() + { + Dial(NameList.AIO_CLRNAME, ""); + var invalidList = new List(); + foreach (var s in _mode.Bypass) + { + if (!Dial(NameList.AIO_BYPNAME, s)) + invalidList.Add(s); + } + + foreach (var s in _mode.Handle) + { + if (!Dial(NameList.AIO_ADDNAME, s)) + invalidList.Add(s); + } + + if (invalidList.Any()) + throw new MessageException(GenerateInvalidRulesMessage(invalidList)); + + // Bypass Self + Dial(NameList.AIO_BYPNAME, "^" + Global.NetchDir.ToRegexString()); + } + + #region DriverUtil + + private static void CheckDriver() + { + var binFileVersion = Utils.Utils.GetFileVersion(Constants.NFDriver); + var systemFileVersion = Utils.Utils.GetFileVersion(SystemDriver); + + Log.Information("Built-in netfilter2 driver version: {Name}", binFileVersion); + Log.Information("Installed netfilter2 driver version: {Name}", systemFileVersion); + + if (!File.Exists(SystemDriver)) + { + // Install + InstallDriver(); + return; + } + + var reinstall = false; + if (Version.TryParse(binFileVersion, out var binResult) && Version.TryParse(systemFileVersion, out var systemResult)) + { + if (binResult.CompareTo(systemResult) > 0) + // Update + reinstall = true; + else if (systemResult.Major != binResult.Major) + // Downgrade when Major version different (may have breaking changes) + reinstall = true; + } + else + { + // Parse File versionName to Version failed + if (!systemFileVersion.Equals(binFileVersion)) + // versionNames are different, Reinstall + reinstall = true; + } + + if (!reinstall) + return; + + Log.Information("Update netfilter2 driver"); + UninstallDriver(); + InstallDriver(); + } + + /// + /// 安装 NF 驱动 + /// + /// 驱动是否安装成功 + private static void InstallDriver() + { + Log.Information("Install netfilter2 driver"); + Global.MainForm.StatusText(i18N.Translate("Installing netfilter2 driver")); + + if (!File.Exists(Constants.NFDriver)) + throw new MessageException(i18N.Translate("builtin driver files missing, can't install NF driver")); + + try + { + File.Copy(Constants.NFDriver, SystemDriver); + } + catch (Exception e) + { + Log.Error(e, "Copy netfilter2.sys failed\n"); + throw new MessageException($"Copy netfilter2.sys failed\n{e.Message}"); + } + + // 注册驱动文件 + if (Interops.Redirector.aio_register("netfilter2")) + { + Log.Information("Install netfilter2 driver finished"); + } + else + { + Log.Error("Register netfilter2 failed"); + } + } + + /// + /// 卸载 NF 驱动 + /// + /// 是否成功卸载 + public static bool UninstallDriver() + { + Log.Information("Uninstall netfilter2"); + try + { + if (NFService.Status == ServiceControllerStatus.Running) + { + NFService.Stop(); + NFService.WaitForStatus(ServiceControllerStatus.Stopped); + } + } + catch (Exception) + { + // ignored + } + + if (!File.Exists(SystemDriver)) + return true; + + Interops.Redirector.aio_unregister("netfilter2"); + File.Delete(SystemDriver); + + return true; + } + + #endregion } \ No newline at end of file diff --git a/Netch/Controllers/PcapController.cs b/Netch/Controllers/PcapController.cs index ae1c28f2..e5338b67 100644 --- a/Netch/Controllers/PcapController.cs +++ b/Netch/Controllers/PcapController.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; +using System.Text; using Microsoft.VisualStudio.Threading; using Netch.Forms; using Netch.Interfaces; @@ -13,93 +8,92 @@ using Netch.Models.Modes.ShareMode; using Netch.Servers; using Netch.Utils; -namespace Netch.Controllers +namespace Netch.Controllers; + +public class PcapController : Guard, IModeController { - public class PcapController : Guard, IModeController + private readonly LogForm _form; + private ShareMode _mode = null!; + private Socks5Server _server = null!; + + public PcapController() : base("pcap2socks.exe", encoding: Encoding.UTF8) { - private readonly LogForm _form; - private ShareMode _mode = null!; - private Socks5Server _server = null!; + _form = new LogForm(Global.MainForm); + _form.CreateControl(); + } - public PcapController() : base("pcap2socks.exe", encoding: Encoding.UTF8) + protected override IEnumerable StartedKeywords { get; } = new[] { "└" }; + + public override string Name => "pcap2socks"; + + public ModeFeature Features => 0; + + public async Task StartAsync(Socks5Server server, Mode mode) + { + if (mode is not ShareMode shareMode) + throw new InvalidOperationException(); + + _server = server; + _mode = shareMode; + + var outboundNetworkInterface = NetworkInterfaceUtils.GetBest(); + + var arguments = new List { - _form = new LogForm(Global.MainForm); - _form.CreateControl(); - } + "--interface", $@"\Device\NPF_{outboundNetworkInterface.Id}", + "--destination", $"{await _server.AutoResolveHostnameAsync()}:{_server.Port}", + _mode.Argument, SpecialArgument.Flag + }; - protected override IEnumerable StartedKeywords { get; } = new[] { "└" }; - - public override string Name => "pcap2socks"; - - public ModeFeature Features => 0; - - public async Task StartAsync(Socks5Server server, Mode mode) - { - if (mode is not ShareMode shareMode) - throw new InvalidOperationException(); - - _server = server; - _mode = shareMode; - - var outboundNetworkInterface = NetworkInterfaceUtils.GetBest(); - - var arguments = new List + if (_server.Auth()) + arguments.AddRange(new[] { - "--interface", $@"\Device\NPF_{outboundNetworkInterface.Id}", - "--destination", $"{await _server.AutoResolveHostnameAsync()}:{_server.Port}", - _mode.Argument, SpecialArgument.Flag - }; + "--username", server.Username, + "--password", server.Password + }); - if (_server.Auth()) - arguments.AddRange(new[] + await StartGuardAsync(Arguments.Format(arguments)); + } + + public override async Task StopAsync() + { + Global.MainForm.Invoke(() => { _form.Close(); }); + await StopGuardAsync(); + } + + ~PcapController() + { + _form.Dispose(); + } + + protected override void OnReadNewLine(string line) + { + Global.MainForm.BeginInvoke(() => + { + if (!_form.IsDisposed) + _form.richTextBox1.AppendText(line + "\n"); + }); + } + + protected override void OnStarted() + { + Global.MainForm.BeginInvoke(() => _form.Show()); + } + + protected override void OnStartFailed() + { + if (new FileInfo(LogPath).Length == 0) + { + Task.Run(() => { - "--username", server.Username, - "--password", server.Password - }); + Thread.Sleep(1000); + Utils.Utils.Open("https://github.com/zhxie/pcap2socks#dependencies"); + }) + .Forget(); - await StartGuardAsync(Arguments.Format(arguments)); + throw new MessageException("Pleases install pcap2socks's dependency"); } - public override async Task StopAsync() - { - Global.MainForm.Invoke(new Action(() => { _form.Close(); })); - await StopGuardAsync(); - } - - ~PcapController() - { - _form.Dispose(); - } - - protected override void OnReadNewLine(string line) - { - Global.MainForm.BeginInvoke(new Action(() => - { - if (!_form.IsDisposed) - _form.richTextBox1.AppendText(line + "\n"); - })); - } - - protected override void OnStarted() - { - Global.MainForm.BeginInvoke(new Action(() => _form.Show())); - } - - protected override void OnStartFailed() - { - if (new FileInfo(LogPath).Length == 0) - { - Task.Run(() => - { - Thread.Sleep(1000); - Utils.Utils.Open("https://github.com/zhxie/pcap2socks#dependencies"); - }) - .Forget(); - - throw new MessageException("Pleases install pcap2socks's dependency"); - } - - Utils.Utils.Open(LogPath); - } + Utils.Utils.Open(LogPath); } } \ No newline at end of file diff --git a/Netch/Controllers/TUNController.cs b/Netch/Controllers/TUNController.cs index fe50c919..09daf7a8 100644 --- a/Netch/Controllers/TUNController.cs +++ b/Netch/Controllers/TUNController.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; +using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; -using System.Threading.Tasks; using Netch.Interfaces; using Netch.Interops; using Netch.Models; @@ -12,124 +8,76 @@ using Netch.Models.Modes; using Netch.Models.Modes.TunMode; using Netch.Servers; using Netch.Utils; -using Serilog; -namespace Netch.Controllers +namespace Netch.Controllers; + +public class TUNController : Guard, IModeController { - public class TUNController : Guard, IModeController + private readonly DNSController _aioDnsController = new(); + + private TunMode _mode = null!; + private IPAddress? _serverRemoteAddress; + private TUNConfig _tunConfig = null!; + private bool _routeSetuped = false; + + private NetRoute _tun; + private NetworkInterface _tunNetworkInterface = null!; + private NetRoute _outbound; + + public override string Name => "tun2socks"; + + public ModeFeature Features => ModeFeature.SupportSocks5Auth; + + protected override IEnumerable StartedKeywords { get; } = new[] { "Creating adapter" }; + + protected override IEnumerable FailedKeywords { get; } = new[] { "panic" }; + + public TUNController() : base("tun2socks.exe") { - private readonly DNSController _aioDnsController = new(); + } - private TunMode _mode = null!; - private IPAddress? _serverRemoteAddress; - private TUNConfig _tunConfig = null!; - private bool _routeSetuped = false; + public async Task StartAsync(Socks5Server server, Mode mode) + { + if (mode is not TunMode tunMode) + throw new InvalidOperationException(); - private NetRoute _tun; - private NetworkInterface _tunNetworkInterface = null!; - private NetRoute _outbound; + _mode = tunMode; + _tunConfig = Global.Settings.TUNTAP; - public override string Name => "tun2socks"; + if (server is Socks5LocalServer socks5Bridge) + _serverRemoteAddress = await DnsUtils.LookupAsync(socks5Bridge.RemoteHostname); + else + _serverRemoteAddress = await DnsUtils.LookupAsync(server.Hostname); - public ModeFeature Features => ModeFeature.SupportSocks5Auth; + if (_serverRemoteAddress != null && IPAddress.IsLoopback(_serverRemoteAddress)) + _serverRemoteAddress = null; - protected override IEnumerable StartedKeywords { get; } = new[] { "Creating adapter" }; + _outbound = NetRoute.GetBestRouteTemplate(); + CheckDriver(); - protected override IEnumerable FailedKeywords { get; } = new[] { "panic" }; + var proxy = server.Auth() + ? $"socks5://{server.Username}:{server.Password}@{await server.AutoResolveHostnameAsync()}:{server.Port}" + : $"socks5://{await server.AutoResolveHostnameAsync()}:{server.Port}"; - public TUNController() : base("tun2socks.exe") + const string interfaceName = "netch"; + var arguments = new object?[] { - } + // -device tun://aioCloud -proxy socks5://127.0.0.1:7890 + "-device", $"tun://{interfaceName}", + "-proxy", proxy, + "-mtu", "1500" + }; - public async Task StartAsync(Socks5Server server, Mode mode) + await StartGuardAsync(Arguments.Format(arguments)); + + // Wait for adapter to be created + for (var i = 0; i < 20; i++) { - if (mode is not TunMode tunMode) - throw new InvalidOperationException(); - - _mode = tunMode; - _tunConfig = Global.Settings.TUNTAP; - - if (server is Socks5LocalServer socks5Bridge) - _serverRemoteAddress = await DnsUtils.LookupAsync(socks5Bridge.RemoteHostname); - else - _serverRemoteAddress = await DnsUtils.LookupAsync(server.Hostname); - - if (_serverRemoteAddress != null && IPAddress.IsLoopback(_serverRemoteAddress)) - _serverRemoteAddress = null; - - _outbound = NetRoute.GetBestRouteTemplate(); - CheckDriver(); - - var proxy = server.Auth() - ? $"socks5://{server.Username}:{server.Password}@{await server.AutoResolveHostnameAsync()}:{server.Port}" - : $"socks5://{await server.AutoResolveHostnameAsync()}:{server.Port}"; - - const string interfaceName = "netch"; - var arguments = new object?[] - { - // -device tun://aioCloud -proxy socks5://127.0.0.1:7890 - "-device", $"tun://{interfaceName}", - "-proxy", proxy, - "-mtu", "1500" - }; - - await StartGuardAsync(Arguments.Format(arguments)); - - // Wait for adapter to be created - for (var i = 0; i < 20; i++) - { - await Task.Delay(300); - try - { - _tunNetworkInterface = NetworkInterfaceUtils.Get(ni => ni.Name.StartsWith(interfaceName)); - break; - } - catch - { - // ignored - } - } - - if (_tunNetworkInterface == null) - throw new MessageException("Create wintun adapter failed"); - - var tunIndex = _tunNetworkInterface.GetIndex(); - _tun = NetRoute.TemplateBuilder(_tunConfig.Gateway, tunIndex); - - if (!RouteHelper.CreateUnicastIP(AddressFamily.InterNetwork, - _tunConfig.Address, - (byte)Utils.Utils.SubnetToCidr(_tunConfig.Netmask), - (ulong)tunIndex)) - { - Log.Error("Create Unicast IP failed"); - throw new MessageException("Create Unicast IP failed"); - } - - await SetupRouteTableAsync(); - } - - public override async Task StopAsync() - { - var tasks = new[] - { - StopGuardAsync(), - Task.Run(ClearRouteTable), - _aioDnsController.StopAsync() - }; - - await Task.WhenAll(tasks); - } - - private void CheckDriver() - { - var f = $@"{Environment.SystemDirectory}\wintun.dll"; + await Task.Delay(300); try { - if (File.Exists(f)) - { - Log.Information($"Remove unused \"{f}\""); - File.Delete(f); - } + _tunNetworkInterface = NetworkInterfaceUtils.Get(ni => ni.Name.StartsWith(interfaceName)); + break; } catch { @@ -137,69 +85,115 @@ namespace Netch.Controllers } } - #region Route + if (_tunNetworkInterface == null) + throw new MessageException("Create wintun adapter failed"); - private async Task SetupRouteTableAsync() + var tunIndex = _tunNetworkInterface.GetIndex(); + _tun = NetRoute.TemplateBuilder(_tunConfig.Gateway, tunIndex); + + if (!RouteHelper.CreateUnicastIP(AddressFamily.InterNetwork, + _tunConfig.Address, + (byte)Utils.Utils.SubnetToCidr(_tunConfig.Netmask), + (ulong)tunIndex)) { - // _outbound: not go through proxy - // _tun: tun -> socks5 - // aiodns: a simple dns server with dns routing - - - _routeSetuped = true; - Global.MainForm.StatusText(i18N.Translate("Setup Route Table Rule")); - - // Server Address - if (_serverRemoteAddress != null) - RouteUtils.CreateRoute(_outbound.FillTemplate(_serverRemoteAddress.ToString(), 32)); - - // Global Bypass IPs - RouteUtils.CreateRouteFill(_outbound, _tunConfig.BypassIPs); - - // rule - RouteUtils.CreateRouteFill(_tun, _mode.Handle); - RouteUtils.CreateRouteFill(_outbound, _mode.Bypass); - - // dns - if (_tunConfig.UseCustomDNS) - { - if (_tunConfig.ProxyDNS) - RouteUtils.CreateRoute(_tun.FillTemplate(_tunConfig.DNS, 32)); - - _tunNetworkInterface.SetDns(_tunConfig.DNS); - } - else - { - // aiodns - RouteUtils.CreateRoute(_outbound.FillTemplate(Utils.Utils.GetHostFromUri(Global.Settings.AioDNS.ChinaDNS), 32)); - RouteUtils.CreateRoute(_tun.FillTemplate(Utils.Utils.GetHostFromUri(Global.Settings.AioDNS.OtherDNS), 32)); - // aiodns listen on tun interface - await _aioDnsController.StartAsync(Global.Settings.TUNTAP.Address); - - _tunNetworkInterface.SetDns(_tunConfig.Address); - } - - // set tun interface's metric to the highest to let Windows use the interface's DNS - NetworkInterfaceUtils.SetInterfaceMetric(_tun.InterfaceIndex, 0); + Log.Error("Create Unicast IP failed"); + throw new MessageException("Create Unicast IP failed"); } - private void ClearRouteTable() - { - if (!_routeSetuped) - return; - - if (_serverRemoteAddress != null) - RouteUtils.DeleteRoute(_outbound.FillTemplate(_serverRemoteAddress.ToString(), 32)); - - RouteUtils.DeleteRouteFill(_outbound, Global.Settings.TUNTAP.BypassIPs); - - RouteUtils.DeleteRouteFill(_outbound, _mode.Bypass); - - RouteUtils.DeleteRoute(_outbound.FillTemplate(Utils.Utils.GetHostFromUri(Global.Settings.AioDNS.ChinaDNS), 32)); - - NetworkInterfaceUtils.SetInterfaceMetric(_outbound.InterfaceIndex); - } - - #endregion + await SetupRouteTableAsync(); } + + public override async Task StopAsync() + { + var tasks = new[] + { + StopGuardAsync(), + Task.Run(ClearRouteTable), + _aioDnsController.StopAsync() + }; + + await Task.WhenAll(tasks); + } + + private void CheckDriver() + { + var f = $@"{Environment.SystemDirectory}\wintun.dll"; + try + { + if (File.Exists(f)) + { + Log.Information($"Remove unused \"{f}\""); + File.Delete(f); + } + } + catch + { + // ignored + } + } + + #region Route + + private async Task SetupRouteTableAsync() + { + // _outbound: not go through proxy + // _tun: tun -> socks5 + // aiodns: a simple dns server with dns routing + + + _routeSetuped = true; + Global.MainForm.StatusText(i18N.Translate("Setup Route Table Rule")); + + // Server Address + if (_serverRemoteAddress != null) + RouteUtils.CreateRoute(_outbound.FillTemplate(_serverRemoteAddress.ToString(), 32)); + + // Global Bypass IPs + RouteUtils.CreateRouteFill(_outbound, _tunConfig.BypassIPs); + + // rule + RouteUtils.CreateRouteFill(_tun, _mode.Handle); + RouteUtils.CreateRouteFill(_outbound, _mode.Bypass); + + // dns + if (_tunConfig.UseCustomDNS) + { + if (_tunConfig.ProxyDNS) + RouteUtils.CreateRoute(_tun.FillTemplate(_tunConfig.DNS, 32)); + + _tunNetworkInterface.SetDns(_tunConfig.DNS); + } + else + { + // aiodns + RouteUtils.CreateRoute(_outbound.FillTemplate(Utils.Utils.GetHostFromUri(Global.Settings.AioDNS.ChinaDNS), 32)); + RouteUtils.CreateRoute(_tun.FillTemplate(Utils.Utils.GetHostFromUri(Global.Settings.AioDNS.OtherDNS), 32)); + // aiodns listen on tun interface + await _aioDnsController.StartAsync(Global.Settings.TUNTAP.Address); + + _tunNetworkInterface.SetDns(_tunConfig.Address); + } + + // set tun interface's metric to the highest to let Windows use the interface's DNS + NetworkInterfaceUtils.SetInterfaceMetric(_tun.InterfaceIndex, 0); + } + + private void ClearRouteTable() + { + if (!_routeSetuped) + return; + + if (_serverRemoteAddress != null) + RouteUtils.DeleteRoute(_outbound.FillTemplate(_serverRemoteAddress.ToString(), 32)); + + RouteUtils.DeleteRouteFill(_outbound, Global.Settings.TUNTAP.BypassIPs); + + RouteUtils.DeleteRouteFill(_outbound, _mode.Bypass); + + RouteUtils.DeleteRoute(_outbound.FillTemplate(Utils.Utils.GetHostFromUri(Global.Settings.AioDNS.ChinaDNS), 32)); + + NetworkInterfaceUtils.SetInterfaceMetric(_outbound.InterfaceIndex); + } + + #endregion } \ No newline at end of file diff --git a/Netch/Controllers/UpdateChecker.cs b/Netch/Controllers/UpdateChecker.cs index 1a01727a..137ff0c8 100644 --- a/Netch/Controllers/UpdateChecker.cs +++ b/Netch/Controllers/UpdateChecker.cs @@ -1,112 +1,106 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; +using System.Net; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Netch.Models.GitHubRelease; using Netch.Utils; -using Serilog; -namespace Netch.Controllers +namespace Netch.Controllers; + +public static class UpdateChecker { - public static class UpdateChecker + public const string Owner = @"NetchX"; + public const string Repo = @"Netch"; + + public const string Name = @"Netch"; + public const string Copyright = @"Copyright © 2019 - 2021"; + + public const string AssemblyVersion = @"1.9.3"; + private const string Suffix = @""; + + public static readonly string Version = $"{AssemblyVersion}{(string.IsNullOrEmpty(Suffix) ? "" : $"-{Suffix}")}"; + + public static Release LatestRelease = null!; + + public static string LatestVersionNumber => LatestRelease.tag_name; + + public static string LatestVersionUrl => LatestRelease.html_url; + + public static event EventHandler? NewVersionFound; + + public static event EventHandler? NewVersionFoundFailed; + + public static event EventHandler? NewVersionNotFound; + + public static async Task CheckAsync(bool isPreRelease) { - public const string Owner = @"NetchX"; - public const string Repo = @"Netch"; - - public const string Name = @"Netch"; - public const string Copyright = @"Copyright © 2019 - 2021"; - - public const string AssemblyVersion = @"1.9.3"; - private const string Suffix = @""; - - public static readonly string Version = $"{AssemblyVersion}{(string.IsNullOrEmpty(Suffix) ? "" : $"-{Suffix}")}"; - - public static Release LatestRelease = null!; - - public static string LatestVersionNumber => LatestRelease.tag_name; - - public static string LatestVersionUrl => LatestRelease.html_url; - - public static event EventHandler? NewVersionFound; - - public static event EventHandler? NewVersionFoundFailed; - - public static event EventHandler? NewVersionNotFound; - - public static async Task CheckAsync(bool isPreRelease) + try { - try + var updater = new GitHubRelease(Owner, Repo); + var url = updater.AllReleaseUrl; + + var (_, json) = await WebUtil.DownloadStringAsync(WebUtil.CreateRequest(url)); + + var releases = JsonSerializer.Deserialize>(json)!; + LatestRelease = GetLatestRelease(releases, isPreRelease); + Log.Information("Github latest release: {Version}", LatestRelease.tag_name); + if (VersionUtil.CompareVersion(LatestRelease.tag_name, Version) > 0) { - var updater = new GitHubRelease(Owner, Repo); - var url = updater.AllReleaseUrl; - - var (_, json) = await WebUtil.DownloadStringAsync(WebUtil.CreateRequest(url)); - - var releases = JsonSerializer.Deserialize>(json)!; - LatestRelease = GetLatestRelease(releases, isPreRelease); - Log.Information("Github latest release: {Version}", LatestRelease.tag_name); - if (VersionUtil.CompareVersion(LatestRelease.tag_name, Version) > 0) - { - Log.Information("Found newer version"); - NewVersionFound?.Invoke(null, EventArgs.Empty); - } - else - { - Log.Information("Already the latest version"); - NewVersionNotFound?.Invoke(null, EventArgs.Empty); - } + Log.Information("Found newer version"); + NewVersionFound?.Invoke(null, EventArgs.Empty); } - catch (Exception e) + else { - if (e is WebException) - Log.Warning(e, "Get releases failed"); - else - Log.Error(e, "Get releases error"); - - NewVersionFoundFailed?.Invoke(null, EventArgs.Empty); + Log.Information("Already the latest version"); + NewVersionNotFound?.Invoke(null, EventArgs.Empty); } } - - public static (string fileName, string sha256) GetLatestUpdateFileNameAndHash(string? keyword = null) + catch (Exception e) { - var matches = Regex.Matches(LatestRelease.body, @"^\| (?.*) \| (?.*) \|\r?$", RegexOptions.Multiline).Skip(2); - /* - Skip(2) - - | 文件名 | SHA256 | - | :- | :- | - */ + if (e is WebException) + Log.Warning(e, "Get releases failed"); + else + Log.Error(e, "Get releases error"); - Match match = keyword == null ? matches.First() : matches.First(m => m.Groups["filename"].Value.Contains(keyword)); - - return (match.Groups["filename"].Value, match.Groups["sha256"].Value); - } - - public static string GetLatestReleaseContent() - { - var sb = new StringBuilder(); - foreach (string l in LatestRelease.body.GetLines(false).SkipWhile(l => l.FirstOrDefault() != '#')) - { - if (l.Contains("校验和")) - break; - - sb.AppendLine(l); - } - - return sb.ToString(); - } - - private static Release GetLatestRelease(IEnumerable releases, bool isPreRelease) - { - if (!isPreRelease) - releases = releases.Where(release => !release.prerelease); - - var ordered = releases.OrderByDescending(release => release.tag_name, new VersionUtil.VersionComparer()); - return ordered.ElementAt(0); + NewVersionFoundFailed?.Invoke(null, EventArgs.Empty); } } + + public static (string fileName, string sha256) GetLatestUpdateFileNameAndHash(string? keyword = null) + { + var matches = Regex.Matches(LatestRelease.body, @"^\| (?.*) \| (?.*) \|\r?$", RegexOptions.Multiline).Skip(2); + /* + Skip(2) + + | 文件名 | SHA256 | + | :- | :- | + */ + + Match match = keyword == null ? matches.First() : matches.First(m => m.Groups["filename"].Value.Contains(keyword)); + + return (match.Groups["filename"].Value, match.Groups["sha256"].Value); + } + + public static string GetLatestReleaseContent() + { + var sb = new StringBuilder(); + foreach (string l in LatestRelease.body.GetLines(false).SkipWhile(l => l.FirstOrDefault() != '#')) + { + if (l.Contains("校验和")) + break; + + sb.AppendLine(l); + } + + return sb.ToString(); + } + + private static Release GetLatestRelease(IEnumerable releases, bool isPreRelease) + { + if (!isPreRelease) + releases = releases.Where(release => !release.prerelease); + + var ordered = releases.OrderByDescending(release => release.tag_name, new VersionUtil.VersionComparer()); + return ordered.ElementAt(0); + } } \ No newline at end of file diff --git a/Netch/Enums/LogLevel.cs b/Netch/Enums/LogLevel.cs index 4edcaea8..c578902b 100644 --- a/Netch/Enums/LogLevel.cs +++ b/Netch/Enums/LogLevel.cs @@ -1,9 +1,8 @@ -namespace Netch.Enums +namespace Netch.Enums; + +public enum LogLevel { - public enum LogLevel - { - INFO, - WARNING, - ERROR - } + INFO, + WARNING, + ERROR } \ No newline at end of file diff --git a/Netch/Enums/State.cs b/Netch/Enums/State.cs index e4d29637..1e31a428 100644 --- a/Netch/Enums/State.cs +++ b/Netch/Enums/State.cs @@ -1,49 +1,48 @@ -namespace Netch.Enums +namespace Netch.Enums; + +/// +/// 状态 +/// +public enum State { /// - /// 状态 + /// 等待命令中 /// - public enum State + Waiting, + + /// + /// 正在启动中 + /// + Starting, + + /// + /// 已启动 + /// + Started, + + /// + /// 正在停止中 + /// + Stopping, + + /// + /// 已停止 + /// + Stopped, + + /// + /// 退出中 + /// + Terminating +} + +public static class StateExtension +{ + public static string GetStatusString(State state) { - /// - /// 等待命令中 - /// - Waiting, + if (state == State.Waiting) + return "Waiting for command"; - /// - /// 正在启动中 - /// - Starting, - - /// - /// 已启动 - /// - Started, - - /// - /// 正在停止中 - /// - Stopping, - - /// - /// 已停止 - /// - Stopped, - - /// - /// 退出中 - /// - Terminating - } - - public static class StateExtension - { - public static string GetStatusString(State state) - { - if (state == State.Waiting) - return "Waiting for command"; - - return state.ToString(); - } + return state.ToString(); } } \ No newline at end of file diff --git a/Netch/Flags.cs b/Netch/Flags.cs index 72984b34..966c29f4 100644 --- a/Netch/Flags.cs +++ b/Netch/Flags.cs @@ -1,13 +1,10 @@ -using System; +namespace Netch; -namespace Netch +public static class Flags { - public static class Flags - { - public static readonly bool IsWindows10Upper = Environment.OSVersion.Version.Major >= 10; + public static readonly bool IsWindows10Upper = Environment.OSVersion.Version.Major >= 10; - public static bool AlwaysShowNewVersionFound { get; set; } + public static bool AlwaysShowNewVersionFound { get; set; } - public static bool NoSupport { get; set; } - } + public static bool NoSupport { get; set; } } \ No newline at end of file diff --git a/Netch/Forms/AboutForm.cs b/Netch/Forms/AboutForm.cs index 42b481d6..076d6d80 100644 --- a/Netch/Forms/AboutForm.cs +++ b/Netch/Forms/AboutForm.cs @@ -1,36 +1,33 @@ using Netch.Properties; using Netch.Utils; -using System; -using System.Windows.Forms; -namespace Netch.Forms +namespace Netch.Forms; + +public partial class AboutForm : Form { - public partial class AboutForm : Form + public AboutForm() { - public AboutForm() - { - InitializeComponent(); - Icon = Resources.icon; - } + InitializeComponent(); + Icon = Resources.icon; + } - private void AboutForm_Load(object sender, EventArgs e) - { - i18N.TranslateForm(this); - } + private void AboutForm_Load(object sender, EventArgs e) + { + i18N.TranslateForm(this); + } - private void NetchPictureBox_Click(object sender, EventArgs e) - { - Utils.Utils.Open("https://github.com/NetchX/Netch"); - } + private void NetchPictureBox_Click(object sender, EventArgs e) + { + Utils.Utils.Open("https://github.com/NetchX/Netch"); + } - private void ChannelLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) - { - Utils.Utils.Open("https://t.me/Netch"); - } + private void ChannelLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + Utils.Utils.Open("https://t.me/Netch"); + } - private void SponsorPictureBox_Click(object sender, EventArgs e) - { - Utils.Utils.Open("https://www.mansora.co"); - } + private void SponsorPictureBox_Click(object sender, EventArgs e) + { + Utils.Utils.Open("https://www.mansora.co"); } } \ No newline at end of file diff --git a/Netch/Forms/BindingForm.cs b/Netch/Forms/BindingForm.cs index 968ea48e..4d9f4355 100644 --- a/Netch/Forms/BindingForm.cs +++ b/Netch/Forms/BindingForm.cs @@ -1,94 +1,89 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows.Forms; -using Netch.Models; +using Netch.Models; -namespace Netch.Forms +namespace Netch.Forms; + +public class BindingForm : Form { - public class BindingForm : Form + private readonly Dictionary> _checkActions = new(); + private readonly Dictionary> _saveActions = new(); + + protected void BindTextBox(TextBoxBase control, Func check, Action save, object value) { - private readonly Dictionary> _checkActions = new(); - private readonly Dictionary> _saveActions = new(); + BindTextBox(control, check, save, value); + } - protected void BindTextBox(TextBoxBase control, Func check, Action save, object value) - { - BindTextBox(control, check, save, value); - } - - protected void BindTextBox(TextBoxBase control, Func check, Action save, object value) - { - control.Text = value.ToString(); - _checkActions.Add(control, - s => + protected void BindTextBox(TextBoxBase control, Func check, Action save, object value) + { + control.Text = value.ToString(); + _checkActions.Add(control, + s => + { + try { - try - { - return check.Invoke((T)Convert.ChangeType(s, typeof(T))); - } - catch - { - return false; - } - }); + return check.Invoke((T)Convert.ChangeType(s, typeof(T))); + } + catch + { + return false; + } + }); - _saveActions.Add(control, c => save.Invoke((T)Convert.ChangeType(((TextBoxBase)c).Text, typeof(T)))); - } + _saveActions.Add(control, c => save.Invoke((T)Convert.ChangeType(((TextBoxBase)c).Text, typeof(T)))); + } - protected void BindCheckBox(CheckBox control, Action save, bool value) - { - control.Checked = value; - _saveActions.Add(control, c => save(((CheckBox)c).Checked)); - } + protected void BindCheckBox(CheckBox control, Action save, bool value) + { + control.Checked = value; + _saveActions.Add(control, c => save(((CheckBox)c).Checked)); + } - protected void BindSyncGlobalCheckBox(SyncGlobalCheckBox control, Action save, bool? value, bool globalValue) - { - control.Value = value; - control.GlobalValue = globalValue; - _saveActions.Add(control, c => save(((SyncGlobalCheckBox)c).Value)); - } + protected void BindSyncGlobalCheckBox(SyncGlobalCheckBox control, Action save, bool? value, bool globalValue) + { + control.Value = value; + control.GlobalValue = globalValue; + _saveActions.Add(control, c => save(((SyncGlobalCheckBox)c).Value)); + } - protected void BindRadioBox(RadioButton control, Action save, bool value) - { - control.Checked = value; - _saveActions.Add(control, c => save.Invoke(((RadioButton)c).Checked)); - } + protected void BindRadioBox(RadioButton control, Action save, bool value) + { + control.Checked = value; + _saveActions.Add(control, c => save.Invoke(((RadioButton)c).Checked)); + } - protected void BindListComboBox(ComboBox comboBox, Action save, IEnumerable values, T value) where T : notnull - { - if (comboBox.DropDownStyle != ComboBoxStyle.DropDownList) - throw new ArgumentOutOfRangeException(); + protected void BindListComboBox(ComboBox comboBox, Action save, IEnumerable values, T value) where T : notnull + { + if (comboBox.DropDownStyle != ComboBoxStyle.DropDownList) + throw new ArgumentOutOfRangeException(); - var tagItems = values.Select(o => new TagItem(o, o.ToString()!)).ToArray(); - comboBox.Items.AddRange(tagItems.Cast().ToArray()); + var tagItems = values.Select(o => new TagItem(o, o.ToString()!)).ToArray(); + comboBox.Items.AddRange(tagItems.Cast().ToArray()); - comboBox.ValueMember = nameof(TagItem.Value); - comboBox.DisplayMember = nameof(TagItem.Text); + comboBox.ValueMember = nameof(TagItem.Value); + comboBox.DisplayMember = nameof(TagItem.Text); - _saveActions.Add(comboBox, c => save.Invoke(((TagItem)((ComboBox)c).SelectedItem).Value)); - Load += (_, _) => { comboBox.SelectedItem = tagItems.SingleOrDefault(t => t.Value.Equals(value)); }; - } + _saveActions.Add(comboBox, c => save.Invoke(((TagItem)((ComboBox)c).SelectedItem).Value)); + Load += (_, _) => { comboBox.SelectedItem = tagItems.SingleOrDefault(t => t.Value.Equals(value)); }; + } - protected void BindComboBox(ComboBox control, Func check, Action save, string value, object[]? values = null) - { - if (values != null) - control.Items.AddRange(values); + protected void BindComboBox(ComboBox control, Func check, Action save, string value, object[]? values = null) + { + if (values != null) + control.Items.AddRange(values); - _saveActions.Add(control, c => save.Invoke(((ComboBox)c).Text)); - _checkActions.Add(control, check.Invoke); + _saveActions.Add(control, c => save.Invoke(((ComboBox)c).Text)); + _checkActions.Add(control, check.Invoke); - Load += (_, _) => { control.Text = value; }; - } + Load += (_, _) => { control.Text = value; }; + } - protected List GetCheckFailedControls() - { - return _checkActions.Where(pair => !pair.Value.Invoke(pair.Key.Text)).Select(pair => pair.Key).ToList(); - } + protected List GetCheckFailedControls() + { + return _checkActions.Where(pair => !pair.Value.Invoke(pair.Key.Text)).Select(pair => pair.Key).ToList(); + } - protected void SaveBinds() - { - foreach (var pair in _saveActions) - pair.Value.Invoke(pair.Key); - } + protected void SaveBinds() + { + foreach (var pair in _saveActions) + pair.Value.Invoke(pair.Key); } } \ No newline at end of file diff --git a/Netch/Forms/GlobalBypassIPForm.cs b/Netch/Forms/GlobalBypassIPForm.cs index 94c3bb23..06d082dd 100644 --- a/Netch/Forms/GlobalBypassIPForm.cs +++ b/Netch/Forms/GlobalBypassIPForm.cs @@ -1,64 +1,60 @@ -using Netch.Properties; +using System.Net; +using Netch.Properties; using Netch.Utils; -using System; -using System.Linq; -using System.Net; -using System.Windows.Forms; -namespace Netch.Forms +namespace Netch.Forms; + +public partial class GlobalBypassIPForm : Form { - public partial class GlobalBypassIPForm : Form + public GlobalBypassIPForm() { - public GlobalBypassIPForm() + InitializeComponent(); + Icon = Resources.icon; + } + + private void GlobalBypassIPForm_Load(object sender, EventArgs e) + { + i18N.TranslateForm(this); + + IPListBox.Items.AddRange(Global.Settings.TUNTAP.BypassIPs.Cast().ToArray()); + + for (var i = 32; i >= 1; i--) + PrefixComboBox.Items.Add(i); + + PrefixComboBox.SelectedIndex = 0; + } + + private void AddButton_Click(object sender, EventArgs e) + { + if (!string.IsNullOrEmpty(IPTextBox.Text)) { - InitializeComponent(); - Icon = Resources.icon; - } - - private void GlobalBypassIPForm_Load(object sender, EventArgs e) - { - i18N.TranslateForm(this); - - IPListBox.Items.AddRange(Global.Settings.TUNTAP.BypassIPs.Cast().ToArray()); - - for (var i = 32; i >= 1; i--) - PrefixComboBox.Items.Add(i); - - PrefixComboBox.SelectedIndex = 0; - } - - private void AddButton_Click(object sender, EventArgs e) - { - if (!string.IsNullOrEmpty(IPTextBox.Text)) - { - if (IPAddress.TryParse(IPTextBox.Text, out var address)) - IPListBox.Items.Add($"{address}/{PrefixComboBox.SelectedItem}"); - else - MessageBoxX.Show(i18N.Translate("Please enter a correct IP address")); - } + if (IPAddress.TryParse(IPTextBox.Text, out var address)) + IPListBox.Items.Add($"{address}/{PrefixComboBox.SelectedItem}"); else - { - MessageBoxX.Show(i18N.Translate("Please enter an IP")); - } + MessageBoxX.Show(i18N.Translate("Please enter a correct IP address")); } - - private void DeleteButton_Click(object sender, EventArgs e) + else { - if (IPListBox.SelectedIndex != -1) - IPListBox.Items.RemoveAt(IPListBox.SelectedIndex); - else - MessageBoxX.Show(i18N.Translate("Please select an IP")); - } - - private async void ControlButton_Click(object sender, EventArgs e) - { - Global.Settings.TUNTAP.BypassIPs.Clear(); - foreach (var ip in IPListBox.Items) - Global.Settings.TUNTAP.BypassIPs.Add((string)ip); - - await Configuration.SaveAsync(); - MessageBoxX.Show(i18N.Translate("Saved")); - Close(); + MessageBoxX.Show(i18N.Translate("Please enter an IP")); } } + + private void DeleteButton_Click(object sender, EventArgs e) + { + if (IPListBox.SelectedIndex != -1) + IPListBox.Items.RemoveAt(IPListBox.SelectedIndex); + else + MessageBoxX.Show(i18N.Translate("Please select an IP")); + } + + private async void ControlButton_Click(object sender, EventArgs e) + { + Global.Settings.TUNTAP.BypassIPs.Clear(); + foreach (var ip in IPListBox.Items) + Global.Settings.TUNTAP.BypassIPs.Add((string)ip); + + await Configuration.SaveAsync(); + MessageBoxX.Show(i18N.Translate("Saved")); + Close(); + } } \ No newline at end of file diff --git a/Netch/Forms/LogForm.cs b/Netch/Forms/LogForm.cs index 77c67253..1ead9918 100644 --- a/Netch/Forms/LogForm.cs +++ b/Netch/Forms/LogForm.cs @@ -1,86 +1,83 @@ -using System; -using System.ComponentModel; -using System.Windows.Forms; +using System.ComponentModel; using Windows.Win32.Foundation; using Windows.Win32.UI.WindowsAndMessaging; using static Windows.Win32.PInvoke; -namespace Netch.Forms +namespace Netch.Forms; + +public partial class LogForm : Form { - public partial class LogForm : Form + private readonly Form _parent; + + public LogForm(Form parent) { - private readonly Form _parent; + InitializeComponent(); + _parent = parent; + } - public LogForm(Form parent) - { - InitializeComponent(); - _parent = parent; - } + protected override void OnLoad(EventArgs? e) + { + base.OnLoad(e); + Parent_Move(null!, null!); + } - protected override void OnLoad(EventArgs? e) - { - base.OnLoad(e); - Parent_Move(null!, null!); - } + private void Parent_Move(object? sender, EventArgs? e) + { + var cl = Location; + var fl = _parent.Location; - private void Parent_Move(object? sender, EventArgs? e) - { - var cl = Location; - var fl = _parent.Location; + cl.X = fl.X + _parent.Width; + cl.Y = fl.Y; + Location = cl; + } - cl.X = fl.X + _parent.Width; - cl.Y = fl.Y; - Location = cl; - } + private void Parent_Activated(object? sender, EventArgs? e) + { + SetWindowPos(new HWND(Handle), + new HWND(-1), + 0, + 0, + 0, + 0, + SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW); - private void Parent_Activated(object? sender, EventArgs? e) - { - SetWindowPos(new HWND(Handle), - new HWND(-1), - 0, - 0, - 0, - 0, - SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW); + SetWindowPos(new HWND(Handle), + new HWND(-2), + 0, + 0, + 0, + 0, + SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW); + } - SetWindowPos(new HWND(Handle), - new HWND(-2), - 0, - 0, - 0, - 0, - SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW); - } + private void richTextBox1_TextChanged(object? sender, EventArgs? e) + { + if (!checkBox1.Checked) + return; - private void richTextBox1_TextChanged(object? sender, EventArgs? e) - { - if (!checkBox1.Checked) - return; + richTextBox1.SelectionStart = richTextBox1.Text.Length; + richTextBox1.ScrollToCaret(); + } - richTextBox1.SelectionStart = richTextBox1.Text.Length; - richTextBox1.ScrollToCaret(); - } + private void LogForm_Load(object? sender, EventArgs? e) + { + _parent.LocationChanged += Parent_Move; + _parent.SizeChanged += Parent_Move; + _parent.Activated += Parent_Activated; + _parent.VisibleChanged += Parent_VisibleChanged; + } - private void LogForm_Load(object? sender, EventArgs? e) - { - _parent.LocationChanged += Parent_Move; - _parent.SizeChanged += Parent_Move; - _parent.Activated += Parent_Activated; - _parent.VisibleChanged += Parent_VisibleChanged; - } + private void Parent_VisibleChanged(object? sender, EventArgs e) + { + Visible = _parent.Visible; + } - private void Parent_VisibleChanged(object? sender, EventArgs e) - { - Visible = _parent.Visible; - } - - protected override void OnClosing(CancelEventArgs? e) - { - _parent.LocationChanged -= Parent_Move; - _parent.SizeChanged -= Parent_Move; - _parent.Activated -= Parent_Activated; - _parent.VisibleChanged -= Parent_VisibleChanged; - base.OnClosing(e!); - } + protected override void OnClosing(CancelEventArgs? e) + { + _parent.LocationChanged -= Parent_Move; + _parent.SizeChanged -= Parent_Move; + _parent.Activated -= Parent_Activated; + _parent.VisibleChanged -= Parent_VisibleChanged; + base.OnClosing(e!); } } \ No newline at end of file diff --git a/Netch/Forms/MainForm.cs b/Netch/Forms/MainForm.cs index 566c2143..cacc1a82 100644 --- a/Netch/Forms/MainForm.cs +++ b/Netch/Forms/MainForm.cs @@ -1,14 +1,6 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.UI.WindowsAndMessaging; @@ -23,1466 +15,1463 @@ using Netch.Models.Modes; using Netch.Properties; using Netch.Services; using Netch.Utils; -using Serilog; -namespace Netch.Forms +namespace Netch.Forms; + +public partial class MainForm : Form { - public partial class MainForm : Form + #region Start + + private readonly Dictionary _mainFormText = new(); + + private bool _textRecorded; + + public MainForm() { - #region Start + InitializeComponent(); + NotifyIcon.Icon = Icon = Resources.icon; - private readonly Dictionary _mainFormText = new(); + AddAddServerToolStripMenuItems(); - private bool _textRecorded; + #region i18N Translations - public MainForm() + if (Flags.NoSupport) + _mainFormText.Add(Name, new[] { "{0} ({1})", "Netch", "No Support" }); + + _mainFormText.Add(UninstallServiceToolStripMenuItem.Name, new[] { "Uninstall {0}", "NF Service" }); + + #endregion + + // 监听电源事件 + SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; + } + + private void AddAddServerToolStripMenuItems() + { + foreach (var serversUtil in ServerHelper.ServerUtilDictionary.Values.OrderBy(i => i.Priority).Where(i => !string.IsNullOrEmpty(i.FullName))) { - InitializeComponent(); - NotifyIcon.Icon = Icon = Resources.icon; + var fullName = serversUtil.FullName; + var control = new ToolStripMenuItem + { + Name = $"Add{fullName}ServerToolStripMenuItem", + Size = new Size(259, 22), + Text = i18N.TranslateFormat("Add [{0}] Server", fullName), + Tag = serversUtil + }; - AddAddServerToolStripMenuItems(); + _mainFormText.Add(control.Name, new[] { "Add [{0}] Server", fullName }); + control.Click += AddServerToolStripMenuItem_Click; + ServerToolStripMenuItem.DropDownItems.Add(control); + } + } - #region i18N Translations + private void MainForm_Load(object sender, EventArgs e) + { + // 计算 ComboBox绘制 目标宽度 + RecordSize(); - if (Flags.NoSupport) - _mainFormText.Add(Name, new[] { "{0} ({1})", "Netch", "No Support" }); + LoadServers(); + SelectLastServer(); + DelayTestHelper.UpdateTick(true); - _mainFormText.Add(UninstallServiceToolStripMenuItem.Name, new[] { "Uninstall {0}", "NF Service" }); + ModeService.Instance.Load(); - #endregion + // 加载翻译 + TranslateControls(); - // 监听电源事件 - SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; + // 隐藏 ConnectivityStatusLabel + ConnectivityStatusVisible(false); + + // 加载快速配置 + LoadProfiles(); + + // 检查更新 + if (Global.Settings.CheckUpdateWhenOpened) + CheckUpdateAsync().Forget(); + + // 检查订阅更新 + if (Global.Settings.UpdateServersWhenOpened) + UpdateServersFromSubscriptionAsync().Forget(); + + // 打开软件时启动加速,产生开始按钮点击事件 + if (Global.Settings.StartWhenOpened) + ControlButton.PerformClick(); + + Program.SingleInstance.StartListenServer(); + } + + private void RecordSize() + { + _numberBoxWidth = ServerComboBox.Width / 10; + _numberBoxX = _numberBoxWidth * 9; + _numberBoxWrap = _numberBoxWidth / 30; + + _configurationGroupBoxHeight = ConfigurationGroupBox.Height; + _profileConfigurationHeight = ConfigurationGroupBox.Controls[0].Height / 3; // 因为 AutoSize, 所以得到的是Controls的总高度 + _profileGroupBoxPaddingHeight = ProfileGroupBox.Height - ProfileTable.Height; + _profileTableHeight = ProfileTable.Height; + } + + private void TranslateControls() + { + #region Record English + + if (!_textRecorded) + { + void RecordText(Component component) + { + try + { + switch (component) + { + case TextBoxBase: + case ListControl: + break; + case Control c: + _mainFormText.Add(c.Name, c.Text); + break; + case ToolStripItem c: + _mainFormText.Add(c.Name, c.Text); + break; + } + } + catch (ArgumentException) + { + // ignored + } + } + + Utils.Utils.ComponentIterator(this, RecordText); + Utils.Utils.ComponentIterator(NotifyMenu, RecordText); + _textRecorded = true; } - private void AddAddServerToolStripMenuItems() - { - foreach (var serversUtil in ServerHelper.ServerUtilDictionary.Values.OrderBy(i => i.Priority) - .Where(i => !string.IsNullOrEmpty(i.FullName))) - { - var fullName = serversUtil.FullName; - var control = new ToolStripMenuItem - { - Name = $"Add{fullName}ServerToolStripMenuItem", - Size = new Size(259, 22), - Text = i18N.TranslateFormat("Add [{0}] Server", fullName), - Tag = serversUtil - }; + #endregion - _mainFormText.Add(control.Name, new[] { "Add [{0}] Server", fullName }); - control.Click += AddServerToolStripMenuItem_Click; - ServerToolStripMenuItem.DropDownItems.Add(control); + #region Translate + + void TranslateText(Component component) + { + switch (component) + { + case TextBoxBase: + case ListControl: + break; + case Control c: + if (_mainFormText.ContainsKey(c.Name)) + c.Text = ControlText(c.Name); + + break; + case ToolStripItem c: + if (_mainFormText.ContainsKey(c.Name)) + c.Text = ControlText(c.Name); + + break; + } + + string ControlText(string name) + { + var value = _mainFormText[name]; + if (value.Equals(string.Empty)) + return string.Empty; + + if (value is object[] values) + return i18N.TranslateFormat((string)values.First(), values.Skip(1).ToArray()); + + return i18N.Translate(value); } } - private void MainForm_Load(object sender, EventArgs e) + Utils.Utils.ComponentIterator(this, TranslateText); + Utils.Utils.ComponentIterator(NotifyMenu, TranslateText); + + #endregion + + UsedBandwidthLabel.Text = $@"{i18N.Translate("Used", ": ")}0 KB"; + State = State; + VersionLabel.Text = UpdateChecker.Version; + } + + #endregion + + #region Controls + + #region MenuStrip + + #region Server + + private async void ImportServersFromClipboardToolStripMenuItem_Click(object sender, EventArgs e) + { + var texts = Clipboard.GetText(); + if (string.IsNullOrWhiteSpace(texts)) + return; + + var servers = ShareLink.ParseText(texts); + foreach (var server in servers) + server.Group = Constants.DefaultGroup; + + Global.Settings.Server.AddRange(servers); + NotifyTip(i18N.TranslateFormat("Import {0} server(s) form Clipboard", servers.Count)); + + LoadServers(); + await Configuration.SaveAsync(); + } + + private async void AddServerToolStripMenuItem_Click([NotNull] object? sender, EventArgs? e) + { + if (sender == null) + throw new ArgumentNullException(nameof(sender)); + + var util = (IServerUtil)((ToolStripMenuItem)sender).Tag; + + Hide(); + util.Create(); + + LoadServers(); + await Configuration.SaveAsync(); + Show(); + } + + #endregion + + #region Mode + + private void CreateProcessModeToolStripButton_Click(object sender, EventArgs e) + { + Hide(); + new ProcessForm().ShowDialog(); + Show(); + } + + private void createRouteTableModeToolStripMenuItem_Click(object sender, EventArgs e) + { + Hide(); + new RouteForm().ShowDialog(); + Show(); + } + + private void ReloadModesToolStripMenuItem_Click(object sender, EventArgs e) + { + Enabled = false; + try { - // 计算 ComboBox绘制 目标宽度 - RecordSize(); - - LoadServers(); - SelectLastServer(); - DelayTestHelper.UpdateTick(true); - ModeService.Instance.Load(); + } + finally + { + Enabled = true; + } + } - // 加载翻译 - TranslateControls(); + #endregion - // 隐藏 ConnectivityStatusLabel - ConnectivityStatusVisible(false); + #region Subscription - // 加载快速配置 - LoadProfiles(); + private void ManageSubscriptionLinksToolStripMenuItem_Click(object sender, EventArgs e) + { + Hide(); + new SubscriptionForm().ShowDialog(); + LoadServers(); + Show(); + } - // 检查更新 - if (Global.Settings.CheckUpdateWhenOpened) - CheckUpdateAsync().Forget(); + private async void UpdateServersFromSubscriptionLinksToolStripMenuItem_Click(object sender, EventArgs e) + { + await UpdateServersFromSubscriptionAsync(); + } - // 检查订阅更新 - if (Global.Settings.UpdateServersWhenOpened) - UpdateServersFromSubscriptionAsync().Forget(); - - // 打开软件时启动加速,产生开始按钮点击事件 - if (Global.Settings.StartWhenOpened) - ControlButton.PerformClick(); - - Program.SingleInstance.StartListenServer(); + private async Task UpdateServersFromSubscriptionAsync() + { + void DisableItems(bool v) + { + MenuStrip.Enabled = ConfigurationGroupBox.Enabled = ProfileGroupBox.Enabled = ControlButton.Enabled = v; } - private void RecordSize() + if (Global.Settings.Subscription.Count <= 0) { - _numberBoxWidth = ServerComboBox.Width / 10; - _numberBoxX = _numberBoxWidth * 9; - _numberBoxWrap = _numberBoxWidth / 30; - - _configurationGroupBoxHeight = ConfigurationGroupBox.Height; - _profileConfigurationHeight = ConfigurationGroupBox.Controls[0].Height / 3; // 因为 AutoSize, 所以得到的是Controls的总高度 - _profileGroupBoxPaddingHeight = ProfileGroupBox.Height - ProfileTable.Height; - _profileTableHeight = ProfileTable.Height; + MessageBoxX.Show(i18N.Translate("No subscription link")); + return; } - private void TranslateControls() + StatusText(i18N.Translate("Updating servers")); + DisableItems(false); + + try { - #region Record English - - if (!_textRecorded) - { - void RecordText(Component component) - { - try - { - switch (component) - { - case TextBoxBase: - case ListControl: - break; - case Control c: - _mainFormText.Add(c.Name, c.Text); - break; - case ToolStripItem c: - _mainFormText.Add(c.Name, c.Text); - break; - } - } - catch (ArgumentException) - { - // ignored - } - } - - Utils.Utils.ComponentIterator(this, RecordText); - Utils.Utils.ComponentIterator(NotifyMenu, RecordText); - _textRecorded = true; - } - - #endregion - - #region Translate - - void TranslateText(Component component) - { - switch (component) - { - case TextBoxBase: - case ListControl: - break; - case Control c: - if (_mainFormText.ContainsKey(c.Name)) - c.Text = ControlText(c.Name); - - break; - case ToolStripItem c: - if (_mainFormText.ContainsKey(c.Name)) - c.Text = ControlText(c.Name); - - break; - } - - string ControlText(string name) - { - var value = _mainFormText[name]; - if (value.Equals(string.Empty)) - return string.Empty; - - if (value is object[] values) - return i18N.TranslateFormat((string)values.First(), values.Skip(1).ToArray()); - - return i18N.Translate(value); - } - } - - Utils.Utils.ComponentIterator(this, TranslateText); - Utils.Utils.ComponentIterator(NotifyMenu, TranslateText); - - #endregion - - UsedBandwidthLabel.Text = $@"{i18N.Translate("Used", ": ")}0 KB"; - State = State; - VersionLabel.Text = UpdateChecker.Version; - } - - #endregion - - #region Controls - - #region MenuStrip - - #region Server - - private async void ImportServersFromClipboardToolStripMenuItem_Click(object sender, EventArgs e) - { - var texts = Clipboard.GetText(); - if (string.IsNullOrWhiteSpace(texts)) - return; - - var servers = ShareLink.ParseText(texts); - foreach (var server in servers) - server.Group = Constants.DefaultGroup; - - Global.Settings.Server.AddRange(servers); - NotifyTip(i18N.TranslateFormat("Import {0} server(s) form Clipboard", servers.Count)); + await SubscriptionUtil.UpdateServersAsync(); LoadServers(); await Configuration.SaveAsync(); + StatusText(i18N.Translate("Servers updated")); + } + catch (Exception e) + { + NotifyTip(i18N.Translate("Unhandled update servers error") + "\n" + e.Message, info: false); + Log.Error(e, "Unhandled Update servers error"); + } + finally + { + DisableItems(true); + } + } + + #endregion + + #region Options + + private async void CheckForUpdatesToolStripMenuItem_Click(object sender, EventArgs e) + { + void OnNewVersionNotFound(object? o, EventArgs? args) + { + NotifyTip(i18N.Translate("Already latest version")); } - private async void AddServerToolStripMenuItem_Click([NotNull] object? sender, EventArgs? e) + void OnNewVersionFoundFailed(object? o, EventArgs? args) { - if (sender == null) - throw new ArgumentNullException(nameof(sender)); - - var util = (IServerUtil)((ToolStripMenuItem)sender).Tag; - - Hide(); - util.Create(); - - LoadServers(); - await Configuration.SaveAsync(); - Show(); + NotifyTip(i18N.Translate("Check for update failed"), info: false); } - #endregion - - #region Mode - - private void CreateProcessModeToolStripButton_Click(object sender, EventArgs e) + try { - Hide(); - new ProcessForm().ShowDialog(); - Show(); + UpdateChecker.NewVersionNotFound += OnNewVersionNotFound; + UpdateChecker.NewVersionFoundFailed += OnNewVersionFoundFailed; + await CheckUpdateAsync(); + } + finally + { + UpdateChecker.NewVersionNotFound -= OnNewVersionNotFound; + UpdateChecker.NewVersionFoundFailed -= OnNewVersionFoundFailed; + } + } + + private void OpenDirectoryToolStripMenuItem_Click(object sender, EventArgs e) + { + Utils.Utils.Open(".\\"); + } + + private async void CleanDNSCacheToolStripMenuItem_Click(object sender, EventArgs e) + { + try + { + await Task.Run(() => + { + NativeMethods.RefreshDNSCache(); + DnsUtils.ClearCache(); + }); + + NotifyTip(i18N.Translate("DNS cache cleanup succeeded")); + } + catch (Exception) + { + // ignored + } + finally + { + StatusText(); + } + } + + private async void UninstallServiceToolStripMenuItem_Click(object sender, EventArgs e) + { + Enabled = false; + StatusText(i18N.TranslateFormat("Uninstalling {0}", "NF Service")); + try + { + var task = Task.Run(NFController.UninstallDriver); + + if (await task) + NotifyTip(i18N.TranslateFormat("{0} has been uninstalled", "NF Service")); + } + finally + { + StatusText(); + Enabled = true; + } + } + + private void RemoveNetchFirewallRulesToolStripMenuItem_Click(object sender, EventArgs e) + { + Firewall.RemoveNetchFwRules(); + } + + private void ShowHideConsoleToolStripMenuItem_Click(object sender, EventArgs e) + { + var windowStyles = (WINDOW_STYLE)PInvoke.GetWindowLong(new HWND(Program.ConsoleHwnd), WINDOW_LONG_PTR_INDEX.GWL_STYLE); + var visible = windowStyles.HasFlag(WINDOW_STYLE.WS_VISIBLE); + PInvoke.ShowWindow(Program.ConsoleHwnd, visible ? SHOW_WINDOW_CMD.SW_HIDE : SHOW_WINDOW_CMD.SW_SHOWNOACTIVATE); + } + + #endregion + + /// + /// 菜单栏强制退出 + /// + private void ForceExitToolStripMenuItem_Click(object sender, EventArgs e) + { + Exit(true); + } + + private void VersionLabel_Click(object sender, EventArgs e) + { + Utils.Utils.Open($"https://github.com/{UpdateChecker.Owner}/{UpdateChecker.Repo}/releases"); + } + + private async void NewVersionLabel_Click(object sender, EventArgs e) + { + if (ModifierKeys == Keys.Control || !UpdateChecker.LatestRelease!.assets.Any()) + { + Utils.Utils.Open(UpdateChecker.LatestVersionUrl!); + return; } - private void createRouteTableModeToolStripMenuItem_Click(object sender, EventArgs e) + if (MessageBoxX.Show(i18N.Translate($"Download and install now?\n\n{UpdateChecker.GetLatestReleaseContent()}"), confirm: true) != + DialogResult.OK) + return; + + NotifyTip(i18N.Translate("Start downloading new version")); + NewVersionLabel.Enabled = false; + NewVersionLabel.Text = "..."; + + try { - Hide(); - new RouteForm().ShowDialog(); - Show(); - } + var progress = new Progress(); + progress.ProgressChanged += (_, percentage) => { NewVersionLabel.Text = $"{percentage}%"; }; - private void ReloadModesToolStripMenuItem_Click(object sender, EventArgs e) - { - Enabled = false; - try - { - ModeService.Instance.Load(); - } - finally - { - Enabled = true; - } - } + string downloadDirectory = Path.Combine(Global.NetchDir, "data"); - #endregion + var (updateFileName, sha256) = UpdateChecker.GetLatestUpdateFileNameAndHash(); + var updateFileUrl = UpdateChecker.LatestRelease.assets[0].browser_download_url!; - #region Subscription + var updateFileFullName = Path.Combine(downloadDirectory, updateFileName); + var updater = new Updater(updateFileFullName, Global.NetchDir); - private void ManageSubscriptionLinksToolStripMenuItem_Click(object sender, EventArgs e) - { - Hide(); - new SubscriptionForm().ShowDialog(); - LoadServers(); - Show(); - } - - private async void UpdateServersFromSubscriptionLinksToolStripMenuItem_Click(object sender, EventArgs e) - { - await UpdateServersFromSubscriptionAsync(); - } - - private async Task UpdateServersFromSubscriptionAsync() - { - void DisableItems(bool v) - { - MenuStrip.Enabled = ConfigurationGroupBox.Enabled = ProfileGroupBox.Enabled = ControlButton.Enabled = v; - } - - if (Global.Settings.Subscription.Count <= 0) - { - MessageBoxX.Show(i18N.Translate("No subscription link")); - return; - } - - StatusText(i18N.Translate("Updating servers")); - DisableItems(false); - - try - { - await SubscriptionUtil.UpdateServersAsync(); - - LoadServers(); - await Configuration.SaveAsync(); - StatusText(i18N.Translate("Servers updated")); - } - catch (Exception e) - { - NotifyTip(i18N.Translate("Unhandled update servers error") + "\n" + e.Message, info: false); - Log.Error(e, "Unhandled Update servers error"); - } - finally - { - DisableItems(true); - } - } - - #endregion - - #region Options - - private async void CheckForUpdatesToolStripMenuItem_Click(object sender, EventArgs e) - { - void OnNewVersionNotFound(object? o, EventArgs? args) - { - NotifyTip(i18N.Translate("Already latest version")); - } - - void OnNewVersionFoundFailed(object? o, EventArgs? args) - { - NotifyTip(i18N.Translate("Check for update failed"), info: false); - } - - try - { - UpdateChecker.NewVersionNotFound += OnNewVersionNotFound; - UpdateChecker.NewVersionFoundFailed += OnNewVersionFoundFailed; - await CheckUpdateAsync(); - } - finally - { - UpdateChecker.NewVersionNotFound -= OnNewVersionNotFound; - UpdateChecker.NewVersionFoundFailed -= OnNewVersionFoundFailed; - } - } - - private void OpenDirectoryToolStripMenuItem_Click(object sender, EventArgs e) - { - Utils.Utils.Open(".\\"); - } - - private async void CleanDNSCacheToolStripMenuItem_Click(object sender, EventArgs e) - { - try - { - await Task.Run(() => - { - NativeMethods.RefreshDNSCache(); - DnsUtils.ClearCache(); - }); - - NotifyTip(i18N.Translate("DNS cache cleanup succeeded")); - } - catch (Exception) - { - // ignored - } - finally - { - StatusText(); - } - } - - private async void UninstallServiceToolStripMenuItem_Click(object sender, EventArgs e) - { - Enabled = false; - StatusText(i18N.TranslateFormat("Uninstalling {0}", "NF Service")); - try - { - var task = Task.Run(NFController.UninstallDriver); - - if (await task) - NotifyTip(i18N.TranslateFormat("{0} has been uninstalled", "NF Service")); - } - finally - { - StatusText(); - Enabled = true; - } - } - - private void RemoveNetchFirewallRulesToolStripMenuItem_Click(object sender, EventArgs e) - { - Firewall.RemoveNetchFwRules(); - } - - private void ShowHideConsoleToolStripMenuItem_Click(object sender, EventArgs e) - { - var windowStyles = (WINDOW_STYLE)PInvoke.GetWindowLong(new HWND(Program.ConsoleHwnd), WINDOW_LONG_PTR_INDEX.GWL_STYLE); - var visible = windowStyles.HasFlag(WINDOW_STYLE.WS_VISIBLE); - PInvoke.ShowWindow(Program.ConsoleHwnd, visible ? SHOW_WINDOW_CMD.SW_HIDE : SHOW_WINDOW_CMD.SW_SHOWNOACTIVATE); - } - - #endregion - - /// - /// 菜单栏强制退出 - /// - private void ForceExitToolStripMenuItem_Click(object sender, EventArgs e) - { - Exit(true); - } - - private void VersionLabel_Click(object sender, EventArgs e) - { - Utils.Utils.Open($"https://github.com/{UpdateChecker.Owner}/{UpdateChecker.Repo}/releases"); - } - - private async void NewVersionLabel_Click(object sender, EventArgs e) - { - if (ModifierKeys == Keys.Control || !UpdateChecker.LatestRelease!.assets.Any()) - { - Utils.Utils.Open(UpdateChecker.LatestVersionUrl!); - return; - } - - if (MessageBoxX.Show(i18N.Translate($"Download and install now?\n\n{UpdateChecker.GetLatestReleaseContent()}"), confirm: true) != - DialogResult.OK) - return; - - NotifyTip(i18N.Translate("Start downloading new version")); - NewVersionLabel.Enabled = false; - NewVersionLabel.Text = "..."; - - try - { - var progress = new Progress(); - progress.ProgressChanged += (_, percentage) => { NewVersionLabel.Text = $"{percentage}%"; }; - - string downloadDirectory = Path.Combine(Global.NetchDir, "data"); - - var (updateFileName, sha256) = UpdateChecker.GetLatestUpdateFileNameAndHash(); - var updateFileUrl = UpdateChecker.LatestRelease.assets[0].browser_download_url!; - - var updateFileFullName = Path.Combine(downloadDirectory, updateFileName); - var updater = new Updater(updateFileFullName, Global.NetchDir); - - var downloaded = false; - if (File.Exists(updateFileFullName)) - if (Utils.Utils.SHA256CheckSum(updateFileFullName) == sha256) - downloaded = true; - else - File.Delete(updateFileFullName); - - if (!downloaded) - { - try - { - await WebUtil.DownloadFileAsync(updateFileUrl, updateFileFullName, progress); - } - catch (Exception e1) - { - Log.Warning(e1, "Download Update File Failed"); - throw new MessageException($"Download Update File Failed: {e1.Message}"); - } - - if (Utils.Utils.SHA256CheckSum(updateFileFullName) != sha256) - throw new MessageException(i18N.Translate("The downloaded file has the wrong hash")); - } - - await StopAsync(); - await Configuration.SaveAsync(); - - // Update - await Task.Run(updater.ApplyUpdate); - - // release mutex, exit - Program.SingleInstance.Dispose(); - Process.Start(Global.NetchExecutable); - Environment.Exit(0); - } - catch (MessageException exception) - { - NotifyTip(exception.Message, info: false); - } - catch (Exception exception) - { - Log.Error(exception, "Unhandled Update error"); - NotifyTip(exception.Message, info: false); - } - finally - { - NewVersionLabel.Visible = false; - NewVersionLabel.Enabled = true; - } - } - - private void AboutToolStripButton_Click(object sender, EventArgs e) - { - Hide(); - new AboutForm().ShowDialog(); - Show(); - } - - private void fAQToolStripMenuItem_Click(object sender, EventArgs e) - { - Utils.Utils.Open("https://docs.netch.org"); - } - - #endregion - - #region ControlButton - - private async void ControlButton_Click(object? sender, EventArgs? e) - { - if (!IsWaiting()) - { - await StopCoreAsync(); - return; - } - - Configuration.SaveAsync().Forget(); - - // 服务器、模式 需选择 - if (ServerComboBox.SelectedItem is not Server server) - { - MessageBoxX.Show(i18N.Translate("Please select a server first")); - return; - } - - if (ModeComboBox.SelectedItem is not Mode mode) - { - MessageBoxX.Show(i18N.Translate("Please select a mode first")); - return; - } - - State = State.Starting; - - try - { - await MainController.StartAsync(server, mode); - } - catch (Exception exception) - { - State = State.Stopped; - StatusText(i18N.Translate("Start failed")); - MessageBoxX.Show(exception.Message, LogLevel.ERROR); - return; - } - - State = State.Started; - - Task.Run(Bandwidth.NetTraffic).Forget(); - DiscoveryNatTypeAsync().Forget(); - HttpConnectAsync().Forget(); - - if (Global.Settings.MinimizeWhenStarted) - Minimize(); - - // 自动检测延迟 - async Task StartedPingAsync() - { - while (State == State.Started) - { - if (Global.Settings.StartedPingInterval >= 0) - { - await server.PingAsync(); - ServerComboBox.Refresh(); - - await Task.Delay(Global.Settings.StartedPingInterval * 1000); - } - else - { - await Task.Delay(5000); - } - } - } - - StartedPingAsync().Forget(); - } - - #endregion - - #region SettingsButton - - private void SettingsButton_Click(object sender, EventArgs e) - { - var oldSettings = Global.Settings.ShallowCopy(); - - Hide(); - new SettingForm().ShowDialog(); - - if (oldSettings.Language != Global.Settings.Language) - { - i18N.Load(Global.Settings.Language); - TranslateControls(); - LoadModes(); - LoadProfiles(); - } - - if (oldSettings.DetectionTick != Global.Settings.DetectionTick) - DelayTestHelper.UpdateTick(true); - - if (oldSettings.ProfileCount != Global.Settings.ProfileCount) - LoadProfiles(); - - Show(); - } - - #endregion - - #region Server - - private void LoadServers() - { - ServerComboBox.Items.Clear(); - ServerComboBox.Items.AddRange(Global.Settings.Server.Cast().ToArray()); - SelectLastServer(); - } - - private void SelectLastServer() - { - // 如果值合法,选中该位置 - if (Global.Settings.ServerComboBoxSelectedIndex > 0 && Global.Settings.ServerComboBoxSelectedIndex < ServerComboBox.Items.Count) - ServerComboBox.SelectedIndex = Global.Settings.ServerComboBoxSelectedIndex; - // 如果值非法,且当前 ServerComboBox 中有元素,选择第一个位置 - else if (ServerComboBox.Items.Count > 0) - ServerComboBox.SelectedIndex = 0; - - // 如果当前 ServerComboBox 中没元素,不做处理 - } - - private void ServerComboBox_SelectionChangeCommitted(object sender, EventArgs o) - { - Global.Settings.ServerComboBoxSelectedIndex = ServerComboBox.SelectedIndex; - } - - private async void EditServerPictureBox_Click(object sender, EventArgs e) - { - // 当前ServerComboBox中至少有一项 - if (!(ServerComboBox.SelectedItem is Server server)) - { - MessageBoxX.Show(i18N.Translate("Please select a server first")); - return; - } - - Hide(); - ServerHelper.GetUtilByTypeName(server.Type).Edit(server); - LoadServers(); - await Configuration.SaveAsync(); - Show(); - } - - private async void SpeedPictureBox_Click(object sender, EventArgs e) - { - void Enable() - { - ServerComboBox.Refresh(); - Enabled = true; - StatusText(); - } - - Enabled = false; - StatusText(i18N.Translate("Testing")); - - if (!IsWaiting() || ModifierKeys == Keys.Control) - { - (ServerComboBox.SelectedItem as Server)?.PingAsync(); - Enable(); - } - else - { - await DelayTestHelper.PerformTestAsync(true); - Enable(); - } - } - - private void CopyLinkPictureBox_Click(object sender, EventArgs e) - { - // 当前ServerComboBox中至少有一项 - if (!(ServerComboBox.SelectedItem is Server server)) - { - MessageBoxX.Show(i18N.Translate("Please select a server first")); - return; - } - - try - { - //听说巨硬BUG经常会炸,所以Catch一下 :D - string text; - if (ModifierKeys == Keys.Control) - text = ShareLink.GetNetchLink(server); + var downloaded = false; + if (File.Exists(updateFileFullName)) + if (Utils.Utils.SHA256CheckSum(updateFileFullName) == sha256) + downloaded = true; else - text = ShareLink.GetShareLink(server); + File.Delete(updateFileFullName); - Clipboard.SetText(text); - } - catch (Exception) + if (!downloaded) { - // ignored - } - } - - private void DeleteServerPictureBox_Click(object sender, EventArgs e) - { - // 当前 ServerComboBox 中至少有一项 - if (!(ServerComboBox.SelectedItem is Server server)) - { - MessageBoxX.Show(i18N.Translate("Please select a server first")); - return; - } - - Global.Settings.Server.Remove(server); - LoadServers(); - } - - #endregion - - #region Mode - - public void LoadModes() - { - if (InvokeRequired) - { - Invoke(new Action(LoadModes)); - return; - } - - ModeComboBox.Items.Clear(); - ModeComboBox.Items.AddRange(Global.Modes.Cast().ToArray()); - ModeComboBox.Tag = null; - SelectLastMode(); - } - - private void SelectLastMode() - { - // 如果值合法,选中该位置 - if (Global.Settings.ModeComboBoxSelectedIndex > 0 && Global.Settings.ModeComboBoxSelectedIndex < ModeComboBox.Items.Count) - ModeComboBox.SelectedIndex = Global.Settings.ModeComboBoxSelectedIndex; - // 如果值非法,且当前 ModeComboBox 中有元素,选择第一个位置 - else if (ModeComboBox.Items.Count > 0) - ModeComboBox.SelectedIndex = 0; - - // 如果当前 ModeComboBox 中没元素,不做处理 - } - - private void ModeComboBox_SelectionChangeCommitted(object sender, EventArgs o) - { - try - { - Global.Settings.ModeComboBoxSelectedIndex = Global.Modes.IndexOf((Mode)ModeComboBox.SelectedItem); - } - catch - { - Global.Settings.ModeComboBoxSelectedIndex = 0; - } - } - - private void EditModePictureBox_Click(object sender, EventArgs e) - { - // 当前ModeComboBox中至少有一项 - if (ModeComboBox.SelectedIndex == -1) - { - MessageBoxX.Show(i18N.Translate("Please select a mode first")); - return; - } - - var mode = (Mode)ModeComboBox.SelectedItem; - if (ModifierKeys == Keys.Control) - { - Utils.Utils.Open(mode.FullName); - return; - } - - switch (mode.Type) - { - case ModeType.ProcessMode: - Hide(); - new ProcessForm(mode).ShowDialog(); - Show(); - break; - case ModeType.TunMode: - Hide(); - new RouteForm(mode).ShowDialog(); - Show(); - break; - case ModeType.ShareMode: - // throw new NotImplementedException(); - default: - Utils.Utils.Open(mode.FullName); - break; - } - } - - private void DeleteModePictureBox_Click(object sender, EventArgs e) - { - // 当前ModeComboBox中至少有一项 - if (ModeComboBox.Items.Count <= 0 || ModeComboBox.SelectedIndex == -1) - { - MessageBoxX.Show(i18N.Translate("Please select a mode first")); - return; - } - - ModeService.Delete((Mode)ModeComboBox.SelectedItem); - SelectLastMode(); - } - - #endregion - - #region Profile - - private int _configurationGroupBoxHeight; - private int _profileConfigurationHeight; - private int _profileGroupBoxPaddingHeight; - private int _profileTableHeight; - - private void LoadProfiles() - { - // Clear - foreach (var button in ProfileTable.Controls) - ((Button)button).Dispose(); - - ProfileTable.Controls.Clear(); - ProfileTable.ColumnStyles.Clear(); - ProfileTable.RowStyles.Clear(); - - var profileCount = Global.Settings.ProfileCount; - if (profileCount == 0) - { - // Hide Profile GroupBox, Change window size - configLayoutPanel.RowStyles[2].SizeType = SizeType.Percent; - configLayoutPanel.RowStyles[2].Height = 0; - ProfileGroupBox.Visible = false; - - ConfigurationGroupBox.Height = _configurationGroupBoxHeight - _profileConfigurationHeight; - } - else - { - // Load Profiles - - if (Global.Settings.ProfileTableColumnCount == 0) - Global.Settings.ProfileTableColumnCount = 5; - - var columnCount = Global.Settings.ProfileTableColumnCount; - - ProfileTable.ColumnCount = profileCount >= columnCount ? columnCount : profileCount; - ProfileTable.RowCount = (int)Math.Ceiling(profileCount / (float)columnCount); - - for (var i = 0; i < profileCount; ++i) + try { - var profile = Global.Settings.Profiles.SingleOrDefault(p => p.Index == i); - var b = new Button - { - Dock = DockStyle.Fill, - Text = profile?.ProfileName ?? i18N.Translate("None"), - Tag = profile - }; - - b.Click += ProfileButton_Click; - ProfileTable.Controls.Add(b, i % columnCount, i / columnCount); + await WebUtil.DownloadFileAsync(updateFileUrl, updateFileFullName, progress); + } + catch (Exception e1) + { + Log.Warning(e1, "Download Update File Failed"); + throw new MessageException($"Download Update File Failed: {e1.Message}"); } - // equal column - for (var i = 1; i <= ProfileTable.RowCount; i++) - ProfileTable.RowStyles.Add(new RowStyle(SizeType.Percent, 1)); - - for (var i = 1; i <= ProfileTable.ColumnCount; i++) - ProfileTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 1)); - - configLayoutPanel.RowStyles[2].SizeType = SizeType.AutoSize; - ProfileGroupBox.Visible = true; - ProfileGroupBox.Height = ProfileTable.RowCount * _profileTableHeight + _profileGroupBoxPaddingHeight; - ConfigurationGroupBox.Height = _configurationGroupBoxHeight; - } - } - - private async void ProfileButton_Click([NotNull] object? sender, EventArgs? e) - { - if (sender == null) - throw new InvalidOperationException(); - - var button = (Button)sender; - var profile = (Profile?)button.Tag; - var index = ProfileTable.Controls.IndexOf(button); - - switch (ModifierKeys) - { - case Keys.Control: - // Save Profile - if (ServerComboBox.SelectedItem is not Server server) - { - MessageBoxX.Show(i18N.Translate("Please select a server first")); - return; - } - - if (ModeComboBox.SelectedItem is not Mode mode) - { - MessageBoxX.Show(i18N.Translate("Please select a mode first")); - return; - } - - var name = ProfileNameText.Text; - - Global.Settings.Profiles.RemoveAll(p => p.Index == index); - profile = new Profile(server, mode, name, index); - Global.Settings.Profiles.Add(profile); - button.Tag = profile; - button.Text = profile.ProfileName; - - ProfileNameText.Clear(); - return; - case Keys.Shift: - // Delete Profile - if (profile == null) - return; - - Global.Settings.Profiles.Remove(profile); - button.Tag = null; - button.Text = i18N.Translate("None"); - return; - } - - // Activate Profile - - if (profile == null) - { - MessageBoxX.Show(i18N.Translate("No saved profile here. Save a profile first by Ctrl+Click on the button")); - return; - } - - try - { - ProfileNameText.Text = profile.ProfileName; - - var server = ServerComboBox.Items.Cast().FirstOrDefault(s => s.Remark.Equals(profile.ServerRemark)); - var mode = ModeComboBox.Items.Cast().FirstOrDefault(m => m.Remark.Any(s => s.Value.Equals(profile.ModeRemark))); - - if (server == null) - throw new MessageException("Server not found."); - - if (mode == null) - throw new MessageException("Mode not found."); - - // set active server and mode - ServerComboBox.SelectedItem = server; - ModeComboBox.SelectedItem = mode; - } - catch (MessageException exception) - { - MessageBoxX.Show(exception.Message, LogLevel.ERROR); - return; + if (Utils.Utils.SHA256CheckSum(updateFileFullName) != sha256) + throw new MessageException(i18N.Translate("The downloaded file has the wrong hash")); } await StopAsync(); - ControlButton.PerformClick(); + await Configuration.SaveAsync(); + + // Update + await Task.Run(updater.ApplyUpdate); + + // release mutex, exit + Program.SingleInstance.Dispose(); + Process.Start(Global.NetchExecutable); + Environment.Exit(0); } - - #endregion - - #region State - - private State _state = State.Waiting; - - /// - /// 当前状态 - /// - public State State + catch (MessageException exception) { - get => _state; - private set - { - void StartDisableItems(bool enabled) - { - ServerComboBox.Enabled = ModeComboBox.Enabled = EditModePictureBox.Enabled = - EditServerPictureBox.Enabled = DeleteModePictureBox.Enabled = DeleteServerPictureBox.Enabled = enabled; - - // 启动需要禁用的控件 - ServerToolStripMenuItem.Enabled = ModeToolStripMenuItem.Enabled = - SubscriptionToolStripMenuItem.Enabled = UninstallServiceToolStripMenuItem.Enabled = enabled; - } - - _state = value; - - DelayTestHelper.Enabled = IsWaiting(_state); - - StatusText(); - switch (value) - { - case State.Waiting: - ControlButton.Enabled = true; - ControlButton.Text = i18N.Translate("Start"); - - break; - case State.Starting: - ControlButton.Enabled = false; - ControlButton.Text = "..."; - - ProfileGroupBox.Enabled = false; - StartDisableItems(false); - break; - case State.Started: - ControlButton.Enabled = true; - ControlButton.Text = i18N.Translate("Stop"); - - ProfileGroupBox.Enabled = true; - - break; - case State.Stopping: - ControlButton.Enabled = false; - ControlButton.Text = "..."; - - ProfileGroupBox.Enabled = false; - BandwidthState(false); - ConnectivityStatusVisible(false); - break; - case State.Stopped: - ControlButton.Enabled = true; - ControlButton.Text = i18N.Translate("Start"); - - LastUploadBandwidth = 0; - LastDownloadBandwidth = 0; - Bandwidth.Stop(); - - ProfileGroupBox.Enabled = true; - StartDisableItems(true); - break; - } - } + NotifyTip(exception.Message, info: false); } - - public async Task StopAsync() + catch (Exception exception) { - if (IsWaiting()) - return; + Log.Error(exception, "Unhandled Update error"); + NotifyTip(exception.Message, info: false); + } + finally + { + NewVersionLabel.Visible = false; + NewVersionLabel.Enabled = true; + } + } + private void AboutToolStripButton_Click(object sender, EventArgs e) + { + Hide(); + new AboutForm().ShowDialog(); + Show(); + } + + private void fAQToolStripMenuItem_Click(object sender, EventArgs e) + { + Utils.Utils.Open("https://docs.netch.org"); + } + + #endregion + + #region ControlButton + + private async void ControlButton_Click(object? sender, EventArgs? e) + { + if (!IsWaiting()) + { await StopCoreAsync(); + return; } - private async Task StopCoreAsync() + Configuration.SaveAsync().Forget(); + + // 服务器、模式 需选择 + if (ServerComboBox.SelectedItem is not Server server) + { + MessageBoxX.Show(i18N.Translate("Please select a server first")); + return; + } + + if (ModeComboBox.SelectedItem is not Mode mode) + { + MessageBoxX.Show(i18N.Translate("Please select a mode first")); + return; + } + + State = State.Starting; + + try + { + await MainController.StartAsync(server, mode); + } + catch (Exception exception) { - State = State.Stopping; - _discoveryNatCts?.Cancel(); - _httpConnectCts?.Cancel(); - await MainController.StopAsync(); State = State.Stopped; + StatusText(i18N.Translate("Start failed")); + MessageBoxX.Show(exception.Message, LogLevel.ERROR); + return; } - private bool IsWaiting() => IsWaiting(_state); + State = State.Started; - private static bool IsWaiting(State state) - { - return state is State.Waiting or State.Stopped; - } + Task.Run(Bandwidth.NetTraffic).Forget(); + DiscoveryNatTypeAsync().Forget(); + HttpConnectAsync().Forget(); - /// - /// 更新状态栏文本 - /// - /// - public void StatusText(string? text = null) + if (Global.Settings.MinimizeWhenStarted) + Minimize(); + + // 自动检测延迟 + async Task StartedPingAsync() { - if (InvokeRequired) + while (State == State.Started) { - BeginInvoke(new Action(StatusText), text); - return; - } - - text ??= i18N.Translate(StateExtension.GetStatusString(State)); - if (_state == State.Started) - text += StatusPortInfoText.Value; - - StatusLabel.Text = i18N.Translate("Status", ": ") + text; - } - - public void BandwidthState(bool state) - { - if (InvokeRequired) - { - BeginInvoke(new Action(BandwidthState), state); - return; - } - - if (IsWaiting()) - return; - - UsedBandwidthLabel.Visible /*= UploadSpeedLabel.Visible*/ = DownloadSpeedLabel.Visible = state; - } - - private void UpdateNatTypeStatusLabelText(string? text, string? country = null) - { - if (!string.IsNullOrEmpty(text)) - { - if (country == null) - NatTypeStatusLabel.Text = $"NAT{i18N.Translate(": ")}{text} "; - else - NatTypeStatusLabel.Text = $"NAT{i18N.Translate(": ")}{text} [{country}]"; - - UpdateNatTypeLight(int.TryParse(text, out var natType) ? natType : -1); - } - else - { - NatTypeStatusLabel.Text = $@"NAT{i18N.Translate(": ", "Test failed")}"; - } - - NatTypeStatusLabel.Visible = true; - } - - private void ConnectivityStatusVisible(bool visible) - { - if (!visible) - HttpStatusLabel.Text = NatTypeStatusLabel.Text = ""; - - HttpStatusLabel.Visible = NatTypeStatusLabel.Visible = NatTypeStatusLightLabel.Visible = visible; - } - - /// - /// 更新 NAT指示灯颜色 - /// - /// NAT Type. keep default(-1) to Hide Light - private void UpdateNatTypeLight(int natType = -1) - { - if (natType > 0 && natType < 5) - { - NatTypeStatusLightLabel.Visible = Flags.IsWindows10Upper; - var c = natType switch + if (Global.Settings.StartedPingInterval >= 0) { - 1 => Color.LimeGreen, - 2 => Color.Yellow, - 3 => Color.Red, - 4 => Color.Black, - _ => throw new ArgumentOutOfRangeException(nameof(natType), natType, null) + await server.PingAsync(); + ServerComboBox.Refresh(); + + await Task.Delay(Global.Settings.StartedPingInterval * 1000); + } + else + { + await Task.Delay(5000); + } + } + } + + StartedPingAsync().Forget(); + } + + #endregion + + #region SettingsButton + + private void SettingsButton_Click(object sender, EventArgs e) + { + var oldSettings = Global.Settings.ShallowCopy(); + + Hide(); + new SettingForm().ShowDialog(); + + if (oldSettings.Language != Global.Settings.Language) + { + i18N.Load(Global.Settings.Language); + TranslateControls(); + LoadModes(); + LoadProfiles(); + } + + if (oldSettings.DetectionTick != Global.Settings.DetectionTick) + DelayTestHelper.UpdateTick(true); + + if (oldSettings.ProfileCount != Global.Settings.ProfileCount) + LoadProfiles(); + + Show(); + } + + #endregion + + #region Server + + private void LoadServers() + { + ServerComboBox.Items.Clear(); + ServerComboBox.Items.AddRange(Global.Settings.Server.Cast().ToArray()); + SelectLastServer(); + } + + private void SelectLastServer() + { + // 如果值合法,选中该位置 + if (Global.Settings.ServerComboBoxSelectedIndex > 0 && Global.Settings.ServerComboBoxSelectedIndex < ServerComboBox.Items.Count) + ServerComboBox.SelectedIndex = Global.Settings.ServerComboBoxSelectedIndex; + // 如果值非法,且当前 ServerComboBox 中有元素,选择第一个位置 + else if (ServerComboBox.Items.Count > 0) + ServerComboBox.SelectedIndex = 0; + + // 如果当前 ServerComboBox 中没元素,不做处理 + } + + private void ServerComboBox_SelectionChangeCommitted(object sender, EventArgs o) + { + Global.Settings.ServerComboBoxSelectedIndex = ServerComboBox.SelectedIndex; + } + + private async void EditServerPictureBox_Click(object sender, EventArgs e) + { + // 当前ServerComboBox中至少有一项 + if (!(ServerComboBox.SelectedItem is Server server)) + { + MessageBoxX.Show(i18N.Translate("Please select a server first")); + return; + } + + Hide(); + ServerHelper.GetUtilByTypeName(server.Type).Edit(server); + LoadServers(); + await Configuration.SaveAsync(); + Show(); + } + + private async void SpeedPictureBox_Click(object sender, EventArgs e) + { + void Enable() + { + ServerComboBox.Refresh(); + Enabled = true; + StatusText(); + } + + Enabled = false; + StatusText(i18N.Translate("Testing")); + + if (!IsWaiting() || ModifierKeys == Keys.Control) + { + (ServerComboBox.SelectedItem as Server)?.PingAsync(); + Enable(); + } + else + { + await DelayTestHelper.PerformTestAsync(true); + Enable(); + } + } + + private void CopyLinkPictureBox_Click(object sender, EventArgs e) + { + // 当前ServerComboBox中至少有一项 + if (!(ServerComboBox.SelectedItem is Server server)) + { + MessageBoxX.Show(i18N.Translate("Please select a server first")); + return; + } + + try + { + //听说巨硬BUG经常会炸,所以Catch一下 :D + string text; + if (ModifierKeys == Keys.Control) + text = ShareLink.GetNetchLink(server); + else + text = ShareLink.GetShareLink(server); + + Clipboard.SetText(text); + } + catch (Exception) + { + // ignored + } + } + + private void DeleteServerPictureBox_Click(object sender, EventArgs e) + { + // 当前 ServerComboBox 中至少有一项 + if (!(ServerComboBox.SelectedItem is Server server)) + { + MessageBoxX.Show(i18N.Translate("Please select a server first")); + return; + } + + Global.Settings.Server.Remove(server); + LoadServers(); + } + + #endregion + + #region Mode + + public void LoadModes() + { + if (InvokeRequired) + { + Invoke(LoadModes); + return; + } + + ModeComboBox.Items.Clear(); + ModeComboBox.Items.AddRange(Global.Modes.Cast().ToArray()); + ModeComboBox.Tag = null; + SelectLastMode(); + } + + private void SelectLastMode() + { + // 如果值合法,选中该位置 + if (Global.Settings.ModeComboBoxSelectedIndex > 0 && Global.Settings.ModeComboBoxSelectedIndex < ModeComboBox.Items.Count) + ModeComboBox.SelectedIndex = Global.Settings.ModeComboBoxSelectedIndex; + // 如果值非法,且当前 ModeComboBox 中有元素,选择第一个位置 + else if (ModeComboBox.Items.Count > 0) + ModeComboBox.SelectedIndex = 0; + + // 如果当前 ModeComboBox 中没元素,不做处理 + } + + private void ModeComboBox_SelectionChangeCommitted(object sender, EventArgs o) + { + try + { + Global.Settings.ModeComboBoxSelectedIndex = Global.Modes.IndexOf((Mode)ModeComboBox.SelectedItem); + } + catch + { + Global.Settings.ModeComboBoxSelectedIndex = 0; + } + } + + private void EditModePictureBox_Click(object sender, EventArgs e) + { + // 当前ModeComboBox中至少有一项 + if (ModeComboBox.SelectedIndex == -1) + { + MessageBoxX.Show(i18N.Translate("Please select a mode first")); + return; + } + + var mode = (Mode)ModeComboBox.SelectedItem; + if (ModifierKeys == Keys.Control) + { + Utils.Utils.Open(mode.FullName); + return; + } + + switch (mode.Type) + { + case ModeType.ProcessMode: + Hide(); + new ProcessForm(mode).ShowDialog(); + Show(); + break; + case ModeType.TunMode: + Hide(); + new RouteForm(mode).ShowDialog(); + Show(); + break; + case ModeType.ShareMode: + // throw new NotImplementedException(); + default: + Utils.Utils.Open(mode.FullName); + break; + } + } + + private void DeleteModePictureBox_Click(object sender, EventArgs e) + { + // 当前ModeComboBox中至少有一项 + if (ModeComboBox.Items.Count <= 0 || ModeComboBox.SelectedIndex == -1) + { + MessageBoxX.Show(i18N.Translate("Please select a mode first")); + return; + } + + ModeService.Delete((Mode)ModeComboBox.SelectedItem); + SelectLastMode(); + } + + #endregion + + #region Profile + + private int _configurationGroupBoxHeight; + private int _profileConfigurationHeight; + private int _profileGroupBoxPaddingHeight; + private int _profileTableHeight; + + private void LoadProfiles() + { + // Clear + foreach (var button in ProfileTable.Controls) + ((Button)button).Dispose(); + + ProfileTable.Controls.Clear(); + ProfileTable.ColumnStyles.Clear(); + ProfileTable.RowStyles.Clear(); + + var profileCount = Global.Settings.ProfileCount; + if (profileCount == 0) + { + // Hide Profile GroupBox, Change window size + configLayoutPanel.RowStyles[2].SizeType = SizeType.Percent; + configLayoutPanel.RowStyles[2].Height = 0; + ProfileGroupBox.Visible = false; + + ConfigurationGroupBox.Height = _configurationGroupBoxHeight - _profileConfigurationHeight; + } + else + { + // Load Profiles + + if (Global.Settings.ProfileTableColumnCount == 0) + Global.Settings.ProfileTableColumnCount = 5; + + var columnCount = Global.Settings.ProfileTableColumnCount; + + ProfileTable.ColumnCount = profileCount >= columnCount ? columnCount : profileCount; + ProfileTable.RowCount = (int)Math.Ceiling(profileCount / (float)columnCount); + + for (var i = 0; i < profileCount; ++i) + { + var profile = Global.Settings.Profiles.SingleOrDefault(p => p.Index == i); + var b = new Button + { + Dock = DockStyle.Fill, + Text = profile?.ProfileName ?? i18N.Translate("None"), + Tag = profile }; - NatTypeStatusLightLabel.ForeColor = c; + b.Click += ProfileButton_Click; + ProfileTable.Controls.Add(b, i % columnCount, i / columnCount); + } + + // equal column + for (var i = 1; i <= ProfileTable.RowCount; i++) + ProfileTable.RowStyles.Add(new RowStyle(SizeType.Percent, 1)); + + for (var i = 1; i <= ProfileTable.ColumnCount; i++) + ProfileTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 1)); + + configLayoutPanel.RowStyles[2].SizeType = SizeType.AutoSize; + ProfileGroupBox.Visible = true; + ProfileGroupBox.Height = ProfileTable.RowCount * _profileTableHeight + _profileGroupBoxPaddingHeight; + ConfigurationGroupBox.Height = _configurationGroupBoxHeight; + } + } + + private async void ProfileButton_Click([NotNull] object? sender, EventArgs? e) + { + if (sender == null) + throw new InvalidOperationException(); + + var button = (Button)sender; + var profile = (Profile?)button.Tag; + var index = ProfileTable.Controls.IndexOf(button); + + switch (ModifierKeys) + { + case Keys.Control: + // Save Profile + if (ServerComboBox.SelectedItem is not Server server) + { + MessageBoxX.Show(i18N.Translate("Please select a server first")); + return; + } + + if (ModeComboBox.SelectedItem is not Mode mode) + { + MessageBoxX.Show(i18N.Translate("Please select a mode first")); + return; + } + + var name = ProfileNameText.Text; + + Global.Settings.Profiles.RemoveAll(p => p.Index == index); + profile = new Profile(server, mode, name, index); + Global.Settings.Profiles.Add(profile); + button.Tag = profile; + button.Text = profile.ProfileName; + + ProfileNameText.Clear(); + return; + case Keys.Shift: + // Delete Profile + if (profile == null) + return; + + Global.Settings.Profiles.Remove(profile); + button.Tag = null; + button.Text = i18N.Translate("None"); + return; + } + + // Activate Profile + + if (profile == null) + { + MessageBoxX.Show(i18N.Translate("No saved profile here. Save a profile first by Ctrl+Click on the button")); + return; + } + + try + { + ProfileNameText.Text = profile.ProfileName; + + var server = ServerComboBox.Items.Cast().FirstOrDefault(s => s.Remark.Equals(profile.ServerRemark)); + var mode = ModeComboBox.Items.Cast().FirstOrDefault(m => m.Remark.Any(s => s.Value.Equals(profile.ModeRemark))); + + if (server == null) + throw new MessageException("Server not found."); + + if (mode == null) + throw new MessageException("Mode not found."); + + // set active server and mode + ServerComboBox.SelectedItem = server; + ModeComboBox.SelectedItem = mode; + } + catch (MessageException exception) + { + MessageBoxX.Show(exception.Message, LogLevel.ERROR); + return; + } + + await StopAsync(); + ControlButton.PerformClick(); + } + + #endregion + + #region State + + private State _state = State.Waiting; + + /// + /// 当前状态 + /// + public State State + { + get => _state; + private set + { + void StartDisableItems(bool enabled) + { + ServerComboBox.Enabled = ModeComboBox.Enabled = EditModePictureBox.Enabled = + EditServerPictureBox.Enabled = DeleteModePictureBox.Enabled = DeleteServerPictureBox.Enabled = enabled; + + // 启动需要禁用的控件 + ServerToolStripMenuItem.Enabled = ModeToolStripMenuItem.Enabled = + SubscriptionToolStripMenuItem.Enabled = UninstallServiceToolStripMenuItem.Enabled = enabled; + } + + _state = value; + + DelayTestHelper.Enabled = IsWaiting(_state); + + StatusText(); + switch (value) + { + case State.Waiting: + ControlButton.Enabled = true; + ControlButton.Text = i18N.Translate("Start"); + + break; + case State.Starting: + ControlButton.Enabled = false; + ControlButton.Text = "..."; + + ProfileGroupBox.Enabled = false; + StartDisableItems(false); + break; + case State.Started: + ControlButton.Enabled = true; + ControlButton.Text = i18N.Translate("Stop"); + + ProfileGroupBox.Enabled = true; + + break; + case State.Stopping: + ControlButton.Enabled = false; + ControlButton.Text = "..."; + + ProfileGroupBox.Enabled = false; + BandwidthState(false); + ConnectivityStatusVisible(false); + break; + case State.Stopped: + ControlButton.Enabled = true; + ControlButton.Text = i18N.Translate("Start"); + + LastUploadBandwidth = 0; + LastDownloadBandwidth = 0; + Bandwidth.Stop(); + + ProfileGroupBox.Enabled = true; + StartDisableItems(true); + break; + } + } + } + + public async Task StopAsync() + { + if (IsWaiting()) + return; + + await StopCoreAsync(); + } + + private async Task StopCoreAsync() + { + State = State.Stopping; + _discoveryNatCts?.Cancel(); + _httpConnectCts?.Cancel(); + await MainController.StopAsync(); + State = State.Stopped; + } + + private bool IsWaiting() => IsWaiting(_state); + + private static bool IsWaiting(State state) + { + return state is State.Waiting or State.Stopped; + } + + /// + /// 更新状态栏文本 + /// + /// + public void StatusText(string? text = null) + { + if (InvokeRequired) + { + BeginInvoke(() => StatusText(text)); + return; + } + + text ??= i18N.Translate(StateExtension.GetStatusString(State)); + if (_state == State.Started) + text += StatusPortInfoText.Value; + + StatusLabel.Text = i18N.Translate("Status", ": ") + text; + } + + public void BandwidthState(bool state) + { + if (InvokeRequired) + { + BeginInvoke(() => BandwidthState(state)); + return; + } + + if (IsWaiting()) + return; + + UsedBandwidthLabel.Visible /*= UploadSpeedLabel.Visible*/ = DownloadSpeedLabel.Visible = state; + } + + private void UpdateNatTypeStatusLabelText(string? text, string? country = null) + { + if (!string.IsNullOrEmpty(text)) + { + if (country == null) + NatTypeStatusLabel.Text = $"NAT{i18N.Translate(": ")}{text} "; + else + NatTypeStatusLabel.Text = $"NAT{i18N.Translate(": ")}{text} [{country}]"; + + UpdateNatTypeLight(int.TryParse(text, out var natType) ? natType : -1); + } + else + { + NatTypeStatusLabel.Text = $@"NAT{i18N.Translate(": ", "Test failed")}"; + } + + NatTypeStatusLabel.Visible = true; + } + + private void ConnectivityStatusVisible(bool visible) + { + if (!visible) + HttpStatusLabel.Text = NatTypeStatusLabel.Text = ""; + + HttpStatusLabel.Visible = NatTypeStatusLabel.Visible = NatTypeStatusLightLabel.Visible = visible; + } + + /// + /// 更新 NAT指示灯颜色 + /// + /// NAT Type. keep default(-1) to Hide Light + private void UpdateNatTypeLight(int natType = -1) + { + if (natType > 0 && natType < 5) + { + NatTypeStatusLightLabel.Visible = Flags.IsWindows10Upper; + var c = natType switch + { + 1 => Color.LimeGreen, + 2 => Color.Yellow, + 3 => Color.Red, + 4 => Color.Black, + _ => throw new ArgumentOutOfRangeException(nameof(natType), natType, null) + }; + + NatTypeStatusLightLabel.ForeColor = c; + } + else + { + NatTypeStatusLightLabel.Visible = false; + } + } + + private async void TcpStatusLabel_Click(object sender, EventArgs e) + { + await HttpConnectAsync(); + } + + private async void NatTypeStatusLabel_Click(object sender, EventArgs e) + { + await DiscoveryNatTypeAsync(); + } + + private CancellationTokenSource? _discoveryNatCts; + + private async Task DiscoveryNatTypeAsync() + { + NatTypeStatusLabel.Enabled = false; + UpdateNatTypeStatusLabelText(i18N.Translate("Testing NAT Type")); + + _discoveryNatCts = new CancellationTokenSource(); + + try + { + var res = await MainController.DiscoveryNatTypeAsync(_discoveryNatCts.Token); + if (_discoveryNatCts.IsCancellationRequested) + return; + + if (!string.IsNullOrEmpty(res.PublicEnd)) + { + var country = await Utils.Utils.GetCityCodeAsync(res.PublicEnd); + + UpdateNatTypeStatusLabelText(res.Result, country); + if (int.TryParse(res.Result, out var natType)) + UpdateNatTypeLight(natType); + else + UpdateNatTypeLight(); } else { + UpdateNatTypeStatusLabelText(res.Result ?? "Error"); NatTypeStatusLightLabel.Visible = false; } } - - private async void TcpStatusLabel_Click(object sender, EventArgs e) + finally { - await HttpConnectAsync(); + _discoveryNatCts.Dispose(); + _discoveryNatCts = null; + NatTypeStatusLabel.Enabled = true; } - - private async void NatTypeStatusLabel_Click(object sender, EventArgs e) - { - await DiscoveryNatTypeAsync(); - } - - private CancellationTokenSource? _discoveryNatCts; - - private async Task DiscoveryNatTypeAsync() - { - NatTypeStatusLabel.Enabled = false; - UpdateNatTypeStatusLabelText(i18N.Translate("Testing NAT Type")); - - _discoveryNatCts = new CancellationTokenSource(); - - try - { - var res = await MainController.DiscoveryNatTypeAsync(_discoveryNatCts.Token); - if (_discoveryNatCts.IsCancellationRequested) - return; - - if (!string.IsNullOrEmpty(res.PublicEnd)) - { - var country = await Utils.Utils.GetCityCodeAsync(res.PublicEnd); - - UpdateNatTypeStatusLabelText(res.Result, country); - if (int.TryParse(res.Result, out var natType)) - UpdateNatTypeLight(natType); - else - UpdateNatTypeLight(); - } - else - { - UpdateNatTypeStatusLabelText(res.Result ?? "Error"); - NatTypeStatusLightLabel.Visible = false; - } - } - finally - { - _discoveryNatCts.Dispose(); - _discoveryNatCts = null; - NatTypeStatusLabel.Enabled = true; - } - } - - private CancellationTokenSource? _httpConnectCts; - - private async Task HttpConnectAsync() - { - HttpStatusLabel.Enabled = false; - - _httpConnectCts = new CancellationTokenSource(); - - try - { - var res = await MainController.HttpConnectAsync(_httpConnectCts.Token); - if (_httpConnectCts.IsCancellationRequested) - return; - - if (res != null) - HttpStatusLabel.Text = $"HTTP{i18N.Translate(": ")}{res}ms"; - else - HttpStatusLabel.Text = $"HTTP{i18N.Translate(": ", "Timeout")}"; - - HttpStatusLabel.Visible = true; - } - finally - { - _httpConnectCts.Dispose(); - _httpConnectCts = null; - HttpStatusLabel.Enabled = true; - } - } - - #endregion - - #endregion - - #region Misc - - #region PowerEvent - - private bool _resumeFlag; - - private async void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) - { - switch (e.Mode) - { - case PowerModes.Suspend: //操作系统即将挂起 - if (!IsWaiting()) - { - _resumeFlag = true; - Log.Information("OS Suspend, Stop"); - await StopAsync(); - } - - break; - case PowerModes.Resume: //操作系统即将从挂起状态继续 - if (_resumeFlag) - { - _resumeFlag = false; - Log.Information("OS Resume, Restart"); - ControlButton.PerformClick(); - } - - break; - } - } - - #endregion - - private void Minimize() - { - // 使关闭时窗口向右下角缩小的效果 - WindowState = FormWindowState.Minimized; - - if (_isFirstCloseWindow) - { - // 显示提示语 - NotifyTip(i18N.Translate("Netch is now minimized to the notification bar, double click this icon to restore.")); - _isFirstCloseWindow = false; - } - - Hide(); - } - - public async void Exit(bool forceExit = false, bool saveConfiguration = true) - { - if (!IsWaiting() && !Global.Settings.StopWhenExited && !forceExit) - { - MessageBoxX.Show(i18N.Translate("Please press Stop button first")); - - ShowMainFormToolStripButton.PerformClick(); - return; - } - - // State = State.Terminating; - NotifyIcon.Visible = false; - Hide(); - - if (saveConfiguration) - await Configuration.SaveAsync(); - - foreach (var file in new[] { Constants.TempConfig, Constants.TempRouteFile }) - if (File.Exists(file)) - File.Delete(file); - - await StopAsync(); - - Dispose(); - Environment.Exit(Environment.ExitCode); - } - - #region FormClosingButton - - private bool _isFirstCloseWindow = true; - - private void MainForm_FormClosing(object sender, FormClosingEventArgs e) - { - if (e.CloseReason == CloseReason.UserClosing && State != State.Terminating) - { - // 取消"关闭窗口"事件 - e.Cancel = true; // 取消关闭窗体 - - // 如果未勾选关闭窗口时退出,隐藏至右下角托盘图标 - if (!Global.Settings.ExitWhenClosed) - Minimize(); - // 如果勾选了关闭时退出,自动点击退出按钮 - else - Exit(); - } - } - - #endregion - - #region Updater - - private async Task CheckUpdateAsync() - { - try - { - UpdateChecker.NewVersionFound += OnUpdateCheckerOnNewVersionFound; - await UpdateChecker.CheckAsync(Global.Settings.CheckBetaUpdate); - if (Flags.AlwaysShowNewVersionFound) - OnUpdateCheckerOnNewVersionFound(null!, null!); - } - finally - { - UpdateChecker.NewVersionFound -= OnUpdateCheckerOnNewVersionFound; - } - - void OnUpdateCheckerOnNewVersionFound(object? o, EventArgs? eventArgs) - { - NotifyTip($"{i18N.Translate(@"New version available", ": ")}{UpdateChecker.LatestVersionNumber}"); - NewVersionLabel.Text = i18N.Translate("New version available"); - NewVersionLabel.Enabled = true; - NewVersionLabel.Visible = true; - } - } - - #endregion - - #region NetTraffic - - /// - /// 上一次下载的流量 - /// - public ulong LastDownloadBandwidth; - - /// - /// 上一次上传的流量 - /// - public ulong LastUploadBandwidth; - - public void OnBandwidthUpdated(ulong download) - { - if (InvokeRequired) - { - BeginInvoke(new Action(OnBandwidthUpdated), download); - return; - } - - try - { - UsedBandwidthLabel.Text = $"{i18N.Translate("Used", ": ")}{Bandwidth.Compute(download)}"; - //UploadSpeedLabel.Text = $"↑: {Utils.Bandwidth.Compute(upload - LastUploadBandwidth)}/s"; - DownloadSpeedLabel.Text = $"↑↓: {Bandwidth.Compute(download - LastDownloadBandwidth)}/s"; - - //LastUploadBandwidth = upload; - LastDownloadBandwidth = download; - Refresh(); - } - catch - { - // ignored - } - } - - #endregion - - #region NotifyIcon - - private void ShowMainFormToolStripButton_Click(object sender, EventArgs e) - { - Utils.Utils.ActivateVisibleWindows(); - } - - /// - /// 通知图标右键菜单退出 - /// - private void ExitToolStripButton_Click(object sender, EventArgs e) - { - Exit(); - } - - private void NotifyIcon_MouseDoubleClick(object? sender, MouseEventArgs? e) - { - ShowMainFormToolStripButton.PerformClick(); - } - - public void NotifyTip(string text, int timeout = 0, bool info = true) - { - // 会阻塞线程 timeout 秒(?) - NotifyIcon.ShowBalloonTip(timeout, UpdateChecker.Name, text, info ? ToolTipIcon.Info : ToolTipIcon.Error); - } - - #endregion - - #region ComboBox_DrawItem - - private readonly SolidBrush _greenBrush = new(Color.FromArgb(50, 255, 56)); - private int _numberBoxWidth; - private int _numberBoxX; - private int _numberBoxWrap; - - private void ComboBox_DrawItem(object sender, DrawItemEventArgs e) - { - if (sender is not ComboBox cbx) - return; - - // 绘制背景颜色 - e.Graphics.FillRectangle(Brushes.White, e.Bounds); - - if (e.Index < 0) - return; - - // 绘制 备注/名称 字符串 - TextRenderer.DrawText(e.Graphics, cbx.Items[e.Index].ToString(), cbx.Font, e.Bounds, Color.Black, TextFormatFlags.Left); - - switch (cbx.Items[e.Index]) - { - case Server item: - { - // 计算延迟底色 - var numBoxBackBrush = item.Delay switch { > 200 => Brushes.Red, > 80 => Brushes.Yellow, >= 0 => _greenBrush, _ => Brushes.Gray }; - - // 绘制延迟底色 - e.Graphics.FillRectangle(numBoxBackBrush, _numberBoxX, e.Bounds.Y, _numberBoxWidth, e.Bounds.Height); - - // 绘制延迟字符串 - TextRenderer.DrawText(e.Graphics, - item.Delay.ToString(), - cbx.Font, - new Point(_numberBoxX + _numberBoxWrap, e.Bounds.Y), - Color.Black, - TextFormatFlags.Left); - - break; - } - case Mode item: - { - /* - // 绘制 模式Box 底色 - e.Graphics.FillRectangle(Brushes.Gray, _numberBoxX, e.Bounds.Y, _numberBoxWidth, e.Bounds.Height); - - // 绘制 模式行数 字符串 - TextRenderer.DrawText(e.Graphics, - item.Content.Count.ToString(), - cbx.Font, - new Point(_numberBoxX + _numberBoxWrap, e.Bounds.Y), - Color.Black, - TextFormatFlags.Left); - */ - - break; - } - } - } - - #endregion - - #endregion } + + private CancellationTokenSource? _httpConnectCts; + + private async Task HttpConnectAsync() + { + HttpStatusLabel.Enabled = false; + + _httpConnectCts = new CancellationTokenSource(); + + try + { + var res = await MainController.HttpConnectAsync(_httpConnectCts.Token); + if (_httpConnectCts.IsCancellationRequested) + return; + + if (res != null) + HttpStatusLabel.Text = $"HTTP{i18N.Translate(": ")}{res}ms"; + else + HttpStatusLabel.Text = $"HTTP{i18N.Translate(": ", "Timeout")}"; + + HttpStatusLabel.Visible = true; + } + finally + { + _httpConnectCts.Dispose(); + _httpConnectCts = null; + HttpStatusLabel.Enabled = true; + } + } + + #endregion + + #endregion + + #region Misc + + #region PowerEvent + + private bool _resumeFlag; + + private async void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) + { + switch (e.Mode) + { + case PowerModes.Suspend: //操作系统即将挂起 + if (!IsWaiting()) + { + _resumeFlag = true; + Log.Information("OS Suspend, Stop"); + await StopAsync(); + } + + break; + case PowerModes.Resume: //操作系统即将从挂起状态继续 + if (_resumeFlag) + { + _resumeFlag = false; + Log.Information("OS Resume, Restart"); + ControlButton.PerformClick(); + } + + break; + } + } + + #endregion + + private void Minimize() + { + // 使关闭时窗口向右下角缩小的效果 + WindowState = FormWindowState.Minimized; + + if (_isFirstCloseWindow) + { + // 显示提示语 + NotifyTip(i18N.Translate("Netch is now minimized to the notification bar, double click this icon to restore.")); + _isFirstCloseWindow = false; + } + + Hide(); + } + + public async void Exit(bool forceExit = false, bool saveConfiguration = true) + { + if (!IsWaiting() && !Global.Settings.StopWhenExited && !forceExit) + { + MessageBoxX.Show(i18N.Translate("Please press Stop button first")); + + ShowMainFormToolStripButton.PerformClick(); + return; + } + + // State = State.Terminating; + NotifyIcon.Visible = false; + Hide(); + + if (saveConfiguration) + await Configuration.SaveAsync(); + + foreach (var file in new[] { Constants.TempConfig, Constants.TempRouteFile }) + if (File.Exists(file)) + File.Delete(file); + + await StopAsync(); + + Dispose(); + Environment.Exit(Environment.ExitCode); + } + + #region FormClosingButton + + private bool _isFirstCloseWindow = true; + + private void MainForm_FormClosing(object sender, FormClosingEventArgs e) + { + if (e.CloseReason == CloseReason.UserClosing && State != State.Terminating) + { + // 取消"关闭窗口"事件 + e.Cancel = true; // 取消关闭窗体 + + // 如果未勾选关闭窗口时退出,隐藏至右下角托盘图标 + if (!Global.Settings.ExitWhenClosed) + Minimize(); + // 如果勾选了关闭时退出,自动点击退出按钮 + else + Exit(); + } + } + + #endregion + + #region Updater + + private async Task CheckUpdateAsync() + { + try + { + UpdateChecker.NewVersionFound += OnUpdateCheckerOnNewVersionFound; + await UpdateChecker.CheckAsync(Global.Settings.CheckBetaUpdate); + if (Flags.AlwaysShowNewVersionFound) + OnUpdateCheckerOnNewVersionFound(null!, null!); + } + finally + { + UpdateChecker.NewVersionFound -= OnUpdateCheckerOnNewVersionFound; + } + + void OnUpdateCheckerOnNewVersionFound(object? o, EventArgs? eventArgs) + { + NotifyTip($"{i18N.Translate(@"New version available", ": ")}{UpdateChecker.LatestVersionNumber}"); + NewVersionLabel.Text = i18N.Translate("New version available"); + NewVersionLabel.Enabled = true; + NewVersionLabel.Visible = true; + } + } + + #endregion + + #region NetTraffic + + /// + /// 上一次下载的流量 + /// + public ulong LastDownloadBandwidth; + + /// + /// 上一次上传的流量 + /// + public ulong LastUploadBandwidth; + + public void OnBandwidthUpdated(ulong download) + { + if (InvokeRequired) + { + BeginInvoke(() => OnBandwidthUpdated(download)); + return; + } + + try + { + UsedBandwidthLabel.Text = $"{i18N.Translate("Used", ": ")}{Bandwidth.Compute(download)}"; + //UploadSpeedLabel.Text = $"↑: {Utils.Bandwidth.Compute(upload - LastUploadBandwidth)}/s"; + DownloadSpeedLabel.Text = $"↑↓: {Bandwidth.Compute(download - LastDownloadBandwidth)}/s"; + + //LastUploadBandwidth = upload; + LastDownloadBandwidth = download; + Refresh(); + } + catch + { + // ignored + } + } + + #endregion + + #region NotifyIcon + + private void ShowMainFormToolStripButton_Click(object sender, EventArgs e) + { + Utils.Utils.ActivateVisibleWindows(); + } + + /// + /// 通知图标右键菜单退出 + /// + private void ExitToolStripButton_Click(object sender, EventArgs e) + { + Exit(); + } + + private void NotifyIcon_MouseDoubleClick(object? sender, MouseEventArgs? e) + { + ShowMainFormToolStripButton.PerformClick(); + } + + public void NotifyTip(string text, int timeout = 0, bool info = true) + { + // 会阻塞线程 timeout 秒(?) + NotifyIcon.ShowBalloonTip(timeout, UpdateChecker.Name, text, info ? ToolTipIcon.Info : ToolTipIcon.Error); + } + + #endregion + + #region ComboBox_DrawItem + + private readonly SolidBrush _greenBrush = new(Color.FromArgb(50, 255, 56)); + private int _numberBoxWidth; + private int _numberBoxX; + private int _numberBoxWrap; + + private void ComboBox_DrawItem(object sender, DrawItemEventArgs e) + { + if (sender is not ComboBox cbx) + return; + + // 绘制背景颜色 + e.Graphics.FillRectangle(Brushes.White, e.Bounds); + + if (e.Index < 0) + return; + + // 绘制 备注/名称 字符串 + TextRenderer.DrawText(e.Graphics, cbx.Items[e.Index].ToString(), cbx.Font, e.Bounds, Color.Black, TextFormatFlags.Left); + + switch (cbx.Items[e.Index]) + { + case Server item: + { + // 计算延迟底色 + var numBoxBackBrush = item.Delay switch { > 200 => Brushes.Red, > 80 => Brushes.Yellow, >= 0 => _greenBrush, _ => Brushes.Gray }; + + // 绘制延迟底色 + e.Graphics.FillRectangle(numBoxBackBrush, _numberBoxX, e.Bounds.Y, _numberBoxWidth, e.Bounds.Height); + + // 绘制延迟字符串 + TextRenderer.DrawText(e.Graphics, + item.Delay.ToString(), + cbx.Font, + new Point(_numberBoxX + _numberBoxWrap, e.Bounds.Y), + Color.Black, + TextFormatFlags.Left); + + break; + } + case Mode item: + { + /* + // 绘制 模式Box 底色 + e.Graphics.FillRectangle(Brushes.Gray, _numberBoxX, e.Bounds.Y, _numberBoxWidth, e.Bounds.Height); + + // 绘制 模式行数 字符串 + TextRenderer.DrawText(e.Graphics, + item.Content.Count.ToString(), + cbx.Font, + new Point(_numberBoxX + _numberBoxWrap, e.Bounds.Y), + Color.Black, + TextFormatFlags.Left); + */ + + break; + } + } + } + + #endregion + + #endregion } \ No newline at end of file diff --git a/Netch/Forms/MessageBoxX.cs b/Netch/Forms/MessageBoxX.cs index 124376d9..4a5d37f8 100644 --- a/Netch/Forms/MessageBoxX.cs +++ b/Netch/Forms/MessageBoxX.cs @@ -1,44 +1,41 @@ -using Netch.Utils; -using System; -using System.Windows.Forms; -using Netch.Enums; +using Netch.Enums; +using Netch.Utils; -namespace Netch.Forms +namespace Netch.Forms; + +public static class MessageBoxX { - public static class MessageBoxX + /// + /// + /// 内容 + /// 自定义标题 + /// 弹窗等级 (标题, 图标) + /// 需要确认 + /// 阻止 owner Focus() 直到 Messageox 被关闭 + public static DialogResult Show(string text, + LogLevel level = LogLevel.INFO, + string title = "", + bool confirm = false, + IWin32Window? owner = null) { - /// - /// - /// 内容 - /// 自定义标题 - /// 弹窗等级 (标题, 图标) - /// 需要确认 - /// 阻止 owner Focus() 直到 Messageox 被关闭 - public static DialogResult Show(string text, - LogLevel level = LogLevel.INFO, - string title = "", - bool confirm = false, - IWin32Window? owner = null) - { - MessageBoxIcon msgIcon; - if (string.IsNullOrWhiteSpace(title)) - title = level switch - { - LogLevel.INFO => "Information", - LogLevel.WARNING => "Warning", - LogLevel.ERROR => "Error", - _ => throw new ArgumentOutOfRangeException(nameof(level), level, null) - }; - - msgIcon = level switch + MessageBoxIcon msgIcon; + if (string.IsNullOrWhiteSpace(title)) + title = level switch { - LogLevel.INFO => MessageBoxIcon.Information, - LogLevel.WARNING => MessageBoxIcon.Warning, - LogLevel.ERROR => MessageBoxIcon.Exclamation, + LogLevel.INFO => "Information", + LogLevel.WARNING => "Warning", + LogLevel.ERROR => "Error", _ => throw new ArgumentOutOfRangeException(nameof(level), level, null) }; - return MessageBox.Show(owner, text, i18N.Translate(title), confirm ? MessageBoxButtons.OKCancel : MessageBoxButtons.OK, msgIcon); - } + msgIcon = level switch + { + LogLevel.INFO => MessageBoxIcon.Information, + LogLevel.WARNING => MessageBoxIcon.Warning, + LogLevel.ERROR => MessageBoxIcon.Exclamation, + _ => throw new ArgumentOutOfRangeException(nameof(level), level, null) + }; + + return MessageBox.Show(owner, text, i18N.Translate(title), confirm ? MessageBoxButtons.OKCancel : MessageBoxButtons.OK, msgIcon); } } \ No newline at end of file diff --git a/Netch/Forms/ModeForms/ModeEditorUtils.cs b/Netch/Forms/ModeForms/ModeEditorUtils.cs index 072cca57..9968ebdc 100644 --- a/Netch/Forms/ModeForms/ModeEditorUtils.cs +++ b/Netch/Forms/ModeForms/ModeEditorUtils.cs @@ -1,27 +1,25 @@ -using System.IO; -using System.Text; +using System.Text; -namespace Netch.Forms.ModeForms +namespace Netch.Forms.ModeForms; + +public static class ModeEditorUtils { - public static class ModeEditorUtils + public static string ToSafeFileName(string text) { - public static string ToSafeFileName(string text) - { - var fileName = new StringBuilder(text); - foreach (var c in Path.GetInvalidFileNameChars()) - fileName.Replace(c, '_'); + var fileName = new StringBuilder(text); + foreach (var c in Path.GetInvalidFileNameChars()) + fileName.Replace(c, '_'); - return fileName.ToString(); - } + return fileName.ToString(); + } - public static string GetCustomModeRelativePath(string name) - { - if (name == string.Empty) - return string.Empty; + public static string GetCustomModeRelativePath(string name) + { + if (name == string.Empty) + return string.Empty; - var safeFileName = ToSafeFileName(name); - var relativePath = $"Custom\\{safeFileName}.json"; - return relativePath; - } + var safeFileName = ToSafeFileName(name); + var relativePath = $"Custom\\{safeFileName}.json"; + return relativePath; } } \ No newline at end of file diff --git a/Netch/Forms/ModeForms/ProcessForm.cs b/Netch/Forms/ModeForms/ProcessForm.cs index e1abff81..d33b02dc 100644 --- a/Netch/Forms/ModeForms/ProcessForm.cs +++ b/Netch/Forms/ModeForms/ProcessForm.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Windows.Forms; +using System.Net; using Netch.Controllers; using Netch.Enums; using Netch.Models.Modes; @@ -12,212 +7,211 @@ using Netch.Properties; using Netch.Services; using Netch.Utils; -namespace Netch.Forms.ModeForms +namespace Netch.Forms.ModeForms; + +public partial class ProcessForm : BindingForm { - public partial class ProcessForm : BindingForm + private readonly bool IsCreateMode; + + private readonly Redirector _mode; + + /// + /// 编辑模式 + /// + /// 模式 + public ProcessForm(Mode? mode = null) { - private readonly bool IsCreateMode; - - private readonly Redirector _mode; - - /// - /// 编辑模式 - /// - /// 模式 - public ProcessForm(Mode? mode = null) + switch (mode) { - switch (mode) - { - case Redirector processMode: - IsCreateMode = false; - _mode = processMode; - break; - case null: - IsCreateMode = true; - _mode = new Redirector(); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - InitializeComponent(); - Icon = Resources.icon; - InitBindings(); - - var g = Global.Settings.Redirector; - BindTextBox(RemarkTextBox, _ => true, s => _mode.i18NRemark = s, _mode.i18NRemark); - BindSyncGlobalCheckBox(HandleTCPCheckBox, b => _mode.FilterTCP = b, _mode.FilterTCP, g.FilterTCP); - BindSyncGlobalCheckBox(HandleUDPCheckBox, b => _mode.FilterUDP = b, _mode.FilterUDP, g.FilterUDP); - BindSyncGlobalCheckBox(HandleDNSCheckBox, b => _mode.FilterDNS = b, _mode.FilterDNS, g.FilterDNS); - BindTextBox(DNSTextBox, s => IPEndPoint.TryParse(s, out _), s => _mode.DNSHost = s, _mode.DNSHost ?? $"{Constants.DefaultPrimaryDNS}:53"); - - BindSyncGlobalCheckBox(HandleProcDNSCheckBox, b => _mode.HandleOnlyDNS = b, _mode.HandleOnlyDNS, g.HandleOnlyDNS); - BindSyncGlobalCheckBox(ProxyDNSCheckBox, b => _mode.DNSProxy = b, _mode.DNSProxy, g.DNSProxy); - BindSyncGlobalCheckBox(HandleICMPCheckBox, b => _mode.FilterICMP = b, _mode.FilterICMP, g.FilterICMP); - BindTextBox(ICMPDelayTextBox, s => s >= 0, s => _mode.ICMPDelay = s, _mode.ICMPDelay ?? 10); - BindCheckBox(HandleLoopbackCheckBox, b => _mode.FilterLoopback = b, _mode.FilterLoopback); - BindCheckBox(HandleLANCheckBox, b => _mode.FilterIntranet = b, _mode.FilterIntranet); - BindSyncGlobalCheckBox(HandleChildProcCheckBox, b => _mode.FilterParent = b, _mode.FilterParent, g.FilterParent); - - BindTextBox(BypassRuleRichTextBox, s => true, s => _mode.Bypass = s.GetLines().ToList(), string.Join(Constants.EOF, _mode.Bypass)); - BindTextBox(HandleRuleRichTextBox, s => true, s => _mode.Handle = s.GetLines().ToList(), string.Join(Constants.EOF, _mode.Handle)); + case Redirector processMode: + IsCreateMode = false; + _mode = processMode; + break; + case null: + IsCreateMode = true; + _mode = new Redirector(); + break; + default: + throw new ArgumentOutOfRangeException(); } - private void InitBindings() + InitializeComponent(); + Icon = Resources.icon; + InitBindings(); + + var g = Global.Settings.Redirector; + BindTextBox(RemarkTextBox, _ => true, s => _mode.i18NRemark = s, _mode.i18NRemark); + BindSyncGlobalCheckBox(HandleTCPCheckBox, b => _mode.FilterTCP = b, _mode.FilterTCP, g.FilterTCP); + BindSyncGlobalCheckBox(HandleUDPCheckBox, b => _mode.FilterUDP = b, _mode.FilterUDP, g.FilterUDP); + BindSyncGlobalCheckBox(HandleDNSCheckBox, b => _mode.FilterDNS = b, _mode.FilterDNS, g.FilterDNS); + BindTextBox(DNSTextBox, s => IPEndPoint.TryParse(s, out _), s => _mode.DNSHost = s, _mode.DNSHost ?? $"{Constants.DefaultPrimaryDNS}:53"); + + BindSyncGlobalCheckBox(HandleProcDNSCheckBox, b => _mode.HandleOnlyDNS = b, _mode.HandleOnlyDNS, g.HandleOnlyDNS); + BindSyncGlobalCheckBox(ProxyDNSCheckBox, b => _mode.DNSProxy = b, _mode.DNSProxy, g.DNSProxy); + BindSyncGlobalCheckBox(HandleICMPCheckBox, b => _mode.FilterICMP = b, _mode.FilterICMP, g.FilterICMP); + BindTextBox(ICMPDelayTextBox, s => s >= 0, s => _mode.ICMPDelay = s, _mode.ICMPDelay ?? 10); + BindCheckBox(HandleLoopbackCheckBox, b => _mode.FilterLoopback = b, _mode.FilterLoopback); + BindCheckBox(HandleLANCheckBox, b => _mode.FilterIntranet = b, _mode.FilterIntranet); + BindSyncGlobalCheckBox(HandleChildProcCheckBox, b => _mode.FilterParent = b, _mode.FilterParent, g.FilterParent); + + BindTextBox(BypassRuleRichTextBox, s => true, s => _mode.Bypass = s.GetLines().ToList(), string.Join(Constants.EOF, _mode.Bypass)); + BindTextBox(HandleRuleRichTextBox, s => true, s => _mode.Handle = s.GetLines().ToList(), string.Join(Constants.EOF, _mode.Handle)); + } + + private void InitBindings() + { + DNSTextBox.DataBindings.Add(new Binding("Enabled", HandleDNSCheckBox, "Checked", true)); + HandleProcDNSCheckBox.DataBindings.Add(new Binding("Enabled", HandleDNSCheckBox, "Checked", true)); + ProxyDNSCheckBox.DataBindings.Add(new Binding("Enabled", HandleDNSCheckBox, "Checked", true)); + ICMPDelayTextBox.DataBindings.Add(new Binding("Enabled", HandleICMPCheckBox, "Checked", true)); + } + + public void ModeForm_Load(object sender, EventArgs e) + { + if (!IsCreateMode) { - DNSTextBox.DataBindings.Add(new Binding("Enabled", HandleDNSCheckBox, "Checked", true)); - HandleProcDNSCheckBox.DataBindings.Add(new Binding("Enabled", HandleDNSCheckBox, "Checked", true)); - ProxyDNSCheckBox.DataBindings.Add(new Binding("Enabled", HandleDNSCheckBox, "Checked", true)); - ICMPDelayTextBox.DataBindings.Add(new Binding("Enabled", HandleICMPCheckBox, "Checked", true)); + Text = "Edit Process Mode"; + + RemarkTextBox.TextChanged -= RemarkTextBox_TextChanged; + RemarkTextBox.Text = _mode.i18NRemark; + FilenameTextBox.Text = ModeService.Instance.GetRelativePath(_mode.FullName); + + if (!_mode.FullName.EndsWith(".json")) + ControlButton.Enabled = false; } - public void ModeForm_Load(object sender, EventArgs e) + i18N.TranslateForm(this); + } + + private void SelectButton_Click(object sender, EventArgs e) + { + RichTextBox ruleRichTextBox; + if (sender == HandleSelectButton) + ruleRichTextBox = HandleRuleRichTextBox; + else if (sender == BypassSelectButton) + ruleRichTextBox = BypassRuleRichTextBox; + else { - if (!IsCreateMode) - { - Text = "Edit Process Mode"; - - RemarkTextBox.TextChanged -= RemarkTextBox_TextChanged; - RemarkTextBox.Text = _mode.i18NRemark; - FilenameTextBox.Text = ModeService.Instance.GetRelativePath(_mode.FullName); - - if (!_mode.FullName.EndsWith(".json")) - ControlButton.Enabled = false; - } - - i18N.TranslateForm(this); + throw new InvalidOperationException(); } - private void SelectButton_Click(object sender, EventArgs e) + var dialog = new FolderBrowserDialog(); + + if (dialog.ShowDialog() == DialogResult.OK) { - RichTextBox ruleRichTextBox; - if (sender == HandleSelectButton) - ruleRichTextBox = HandleRuleRichTextBox; - else if (sender == BypassSelectButton) - ruleRichTextBox = BypassRuleRichTextBox; - else - { - throw new InvalidOperationException(); - } + var path = dialog.SelectedPath; + if (!path.EndsWith(@"\")) + path += @"\"; - var dialog = new FolderBrowserDialog(); - - if (dialog.ShowDialog() == DialogResult.OK) - { - var path = dialog.SelectedPath; - if (!path.EndsWith(@"\")) - path += @"\"; - - AppendText(ruleRichTextBox, $"^{path.ToRegexString()}"); - } - } - - private static void AppendText(Control ruleTextBox, string value) - { - if (ruleTextBox.Text.Any()) - ruleTextBox.Text = ruleTextBox.Text.Trim() + Constants.EOF + value; - else - ruleTextBox.Text = value; - } - - public void ControlButton_Click(object sender, EventArgs e) - { - if (string.IsNullOrWhiteSpace(RemarkTextBox.Text)) - { - MessageBoxX.Show(i18N.Translate("Please enter a mode remark")); - return; - } - - SaveBinds(); - - if (IsCreateMode) - { - var relativePath = FilenameTextBox.Text; - var fullName = ModeService.Instance.GetFullPath(relativePath); - if (File.Exists(fullName)) - { - MessageBoxX.Show(i18N.Translate("File already exists.\n Please Change the filename")); - return; - } - - _mode.FullName = fullName; - - ModeService.Instance.Add(_mode); - MessageBoxX.Show(i18N.Translate("Mode added successfully")); - } - else - { - _mode.WriteFile(); - MessageBoxX.Show(i18N.Translate("Mode updated successfully")); - } - - Close(); - } - - private void RemarkTextBox_TextChanged(object? sender, EventArgs? e) - { - if (!IsHandleCreated) - return; - - BeginInvoke(new Action(() => - { - FilenameTextBox.Text = FilenameTextBox.Text = ModeEditorUtils.GetCustomModeRelativePath(RemarkTextBox.Text); - })); - } - - private void ScanButton_Click(object sender, EventArgs e) - { - RichTextBox ruleRichTextBox; - if (sender == HandleScanButton) - ruleRichTextBox = HandleRuleRichTextBox; - else if (sender == BypassScanButton) - ruleRichTextBox = BypassRuleRichTextBox; - else - { - throw new InvalidOperationException(); - } - - var dialog = new FolderBrowserDialog(); - - if (dialog.ShowDialog() == DialogResult.OK) - { - var path = dialog.SelectedPath; - var list = new List(); - const uint maxCount = 50; - try - { - ScanDirectory(path, list, maxCount); - } - catch - { - MessageBoxX.Show(i18N.Translate($"The number of executable files in the \"{path}\" directory is greater than {maxCount}"), - LogLevel.WARNING); - - return; - } - - AppendText(ruleRichTextBox, string.Join(Constants.EOF, list)); - } - } - - private void ScanDirectory(string directory, List list, uint maxCount = 30) - { - foreach (var dir in Directory.GetDirectories(directory)) - ScanDirectory(dir, list, maxCount); - - list.AddRange( - Directory.GetFiles(directory).Select(s => Path.GetFileName(s)).Where(s => s.EndsWith(".exe")).Select(s => s.ToRegexString())); - - if (maxCount != 0 && list.Count > maxCount) - throw new Exception("The number of results is greater than maxCount"); - } - - private void ValidationButton_Click(object sender, EventArgs e) - { - if (!NFController.CheckRules(_mode.Bypass, out var results)) - MessageBoxX.Show(NFController.GenerateInvalidRulesMessage(results), LogLevel.WARNING); - else - MessageBoxX.Show("Fine"); + AppendText(ruleRichTextBox, $"^{path.ToRegexString()}"); } } + + private static void AppendText(Control ruleTextBox, string value) + { + if (ruleTextBox.Text.Any()) + ruleTextBox.Text = ruleTextBox.Text.Trim() + Constants.EOF + value; + else + ruleTextBox.Text = value; + } + + public void ControlButton_Click(object sender, EventArgs e) + { + if (string.IsNullOrWhiteSpace(RemarkTextBox.Text)) + { + MessageBoxX.Show(i18N.Translate("Please enter a mode remark")); + return; + } + + SaveBinds(); + + if (IsCreateMode) + { + var relativePath = FilenameTextBox.Text; + var fullName = ModeService.Instance.GetFullPath(relativePath); + if (File.Exists(fullName)) + { + MessageBoxX.Show(i18N.Translate("File already exists.\n Please Change the filename")); + return; + } + + _mode.FullName = fullName; + + ModeService.Instance.Add(_mode); + MessageBoxX.Show(i18N.Translate("Mode added successfully")); + } + else + { + _mode.WriteFile(); + MessageBoxX.Show(i18N.Translate("Mode updated successfully")); + } + + Close(); + } + + private void RemarkTextBox_TextChanged(object? sender, EventArgs? e) + { + if (!IsHandleCreated) + return; + + BeginInvoke(() => + { + FilenameTextBox.Text = FilenameTextBox.Text = ModeEditorUtils.GetCustomModeRelativePath(RemarkTextBox.Text); + }); + } + + private void ScanButton_Click(object sender, EventArgs e) + { + RichTextBox ruleRichTextBox; + if (sender == HandleScanButton) + ruleRichTextBox = HandleRuleRichTextBox; + else if (sender == BypassScanButton) + ruleRichTextBox = BypassRuleRichTextBox; + else + { + throw new InvalidOperationException(); + } + + var dialog = new FolderBrowserDialog(); + + if (dialog.ShowDialog() == DialogResult.OK) + { + var path = dialog.SelectedPath; + var list = new List(); + const uint maxCount = 50; + try + { + ScanDirectory(path, list, maxCount); + } + catch + { + MessageBoxX.Show(i18N.Translate($"The number of executable files in the \"{path}\" directory is greater than {maxCount}"), + LogLevel.WARNING); + + return; + } + + AppendText(ruleRichTextBox, string.Join(Constants.EOF, list)); + } + } + + private void ScanDirectory(string directory, List list, uint maxCount = 30) + { + foreach (var dir in Directory.GetDirectories(directory)) + ScanDirectory(dir, list, maxCount); + + list.AddRange( + Directory.GetFiles(directory).Select(s => Path.GetFileName(s)).Where(s => s.EndsWith(".exe")).Select(s => s.ToRegexString())); + + if (maxCount != 0 && list.Count > maxCount) + throw new Exception("The number of results is greater than maxCount"); + } + + private void ValidationButton_Click(object sender, EventArgs e) + { + if (!NFController.CheckRules(_mode.Bypass, out var results)) + MessageBoxX.Show(NFController.GenerateInvalidRulesMessage(results), LogLevel.WARNING); + else + MessageBoxX.Show("Fine"); + } } \ No newline at end of file diff --git a/Netch/Forms/ModeForms/RouteForm.cs b/Netch/Forms/ModeForms/RouteForm.cs index 7909c516..40037083 100644 --- a/Netch/Forms/ModeForms/RouteForm.cs +++ b/Netch/Forms/ModeForms/RouteForm.cs @@ -1,109 +1,105 @@ -using System; -using System.IO; -using System.Linq; -using Netch.Models.Modes; +using Netch.Models.Modes; using Netch.Models.Modes.TunMode; using Netch.Properties; using Netch.Services; using Netch.Utils; -namespace Netch.Forms.ModeForms +namespace Netch.Forms.ModeForms; + +public partial class RouteForm : BindingForm { - public partial class RouteForm : BindingForm + private readonly bool IsCreateMode; + + private readonly TunMode _mode; + + public RouteForm(Mode? mode = null) { - private readonly bool IsCreateMode; - - private readonly TunMode _mode; - - public RouteForm(Mode? mode = null) + switch (mode) { - switch (mode) - { - case null: - IsCreateMode = true; - _mode = new TunMode(); - break; - case TunMode tunMode: - IsCreateMode = false; - _mode = tunMode; - break; - default: - throw new ArgumentOutOfRangeException(); - } - - InitializeComponent(); - Icon = Resources.icon; - - - BindTextBox(RemarkTextBox, _ => true, s => _mode.i18NRemark = s, _mode.i18NRemark); - // TODO Options Not implemented - - BindTextBox(BypassRuleRichTextBox, s => true, s => _mode.Bypass = s.GetLines().ToList(), string.Join(Constants.EOF, _mode.Bypass)); - BindTextBox(HandleRuleRichTextBox, s => true, s => _mode.Handle = s.GetLines().ToList(), string.Join(Constants.EOF, _mode.Handle)); + case null: + IsCreateMode = true; + _mode = new TunMode(); + break; + case TunMode tunMode: + IsCreateMode = false; + _mode = tunMode; + break; + default: + throw new ArgumentOutOfRangeException(); } - private void Route_Load(object sender, EventArgs e) + InitializeComponent(); + Icon = Resources.icon; + + + BindTextBox(RemarkTextBox, _ => true, s => _mode.i18NRemark = s, _mode.i18NRemark); + // TODO Options Not implemented + + BindTextBox(BypassRuleRichTextBox, s => true, s => _mode.Bypass = s.GetLines().ToList(), string.Join(Constants.EOF, _mode.Bypass)); + BindTextBox(HandleRuleRichTextBox, s => true, s => _mode.Handle = s.GetLines().ToList(), string.Join(Constants.EOF, _mode.Handle)); + } + + private void Route_Load(object sender, EventArgs e) + { + if (!IsCreateMode) { - if (!IsCreateMode) - { - Text = "Edit Route Table Rule"; + Text = "Edit Route Table Rule"; - RemarkTextBox.TextChanged -= RemarkTextBox_TextChanged; - RemarkTextBox.Text = _mode.i18NRemark; - FilenameTextBox.Text = ModeService.Instance.GetRelativePath(_mode.FullName); + RemarkTextBox.TextChanged -= RemarkTextBox_TextChanged; + RemarkTextBox.Text = _mode.i18NRemark; + FilenameTextBox.Text = ModeService.Instance.GetRelativePath(_mode.FullName); - if (!_mode.FullName.EndsWith(".json")) - ControlButton.Enabled = false; - } - - i18N.TranslateForm(this); + if (!_mode.FullName.EndsWith(".json")) + ControlButton.Enabled = false; } - private void ControlButton_Click(object sender, EventArgs e) + i18N.TranslateForm(this); + } + + private void ControlButton_Click(object sender, EventArgs e) + { + if (string.IsNullOrWhiteSpace(RemarkTextBox.Text)) { - if (string.IsNullOrWhiteSpace(RemarkTextBox.Text)) + MessageBoxX.Show(i18N.Translate("Please enter a mode remark")); + return; + } + + SaveBinds(); + + if (IsCreateMode) + { + var relativePath = FilenameTextBox.Text; + var fullName = ModeService.Instance.GetFullPath(relativePath); + if (File.Exists(fullName)) { - MessageBoxX.Show(i18N.Translate("Please enter a mode remark")); + MessageBoxX.Show(i18N.Translate("File already exists.\n Please Change the filename")); return; } - SaveBinds(); + _mode.FullName = fullName; - if (IsCreateMode) - { - var relativePath = FilenameTextBox.Text; - var fullName = ModeService.Instance.GetFullPath(relativePath); - if (File.Exists(fullName)) - { - MessageBoxX.Show(i18N.Translate("File already exists.\n Please Change the filename")); - return; - } - - _mode.FullName = fullName; - - ModeService.Instance.Add(_mode); - MessageBoxX.Show(i18N.Translate("Mode added successfully")); - } - else - { - _mode.WriteFile(); - MessageBoxX.Show(i18N.Translate("Mode updated successfully")); - ModeService.Instance.Sort(); - } - - Global.MainForm.ModeComboBox.SelectedItem = _mode; - Close(); + ModeService.Instance.Add(_mode); + MessageBoxX.Show(i18N.Translate("Mode added successfully")); } - - private void RemarkTextBox_TextChanged(object? sender, EventArgs? e) + else { - if (!IsHandleCreated) - return; - - BeginInvoke(new Action(() => - { - FilenameTextBox.Text = FilenameTextBox.Text = ModeEditorUtils.GetCustomModeRelativePath(RemarkTextBox.Text); - })); + _mode.WriteFile(); + MessageBoxX.Show(i18N.Translate("Mode updated successfully")); + ModeService.Instance.Sort(); } + + Global.MainForm.ModeComboBox.SelectedItem = _mode; + Close(); + } + + private void RemarkTextBox_TextChanged(object? sender, EventArgs? e) + { + if (!IsHandleCreated) + return; + + BeginInvoke(() => + { + FilenameTextBox.Text = FilenameTextBox.Text = ModeEditorUtils.GetCustomModeRelativePath(RemarkTextBox.Text); + }); } } \ No newline at end of file diff --git a/Netch/Forms/ServerForm.cs b/Netch/Forms/ServerForm.cs index 1d8cc253..fb6d6c79 100644 --- a/Netch/Forms/ServerForm.cs +++ b/Netch/Forms/ServerForm.cs @@ -1,327 +1,321 @@ #nullable disable -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Drawing; -using System.Linq; -using System.Windows.Forms; using Netch.Models; using Netch.Properties; using Netch.Utils; -namespace Netch.Forms +namespace Netch.Forms; + +[DesignerCategory(@"Code")] +public abstract class ServerForm : Form { - [DesignerCategory(@"Code")] - public abstract class ServerForm : Form + private const int ControlLineHeight = 28; + private const int InputBoxWidth = 294; + + private readonly Dictionary> _checkActions = new(); + + private readonly Dictionary> _saveActions = new(); + + private int _controlLines = 2; + private Label AddressLabel; + protected TextBox AddressTextBox; + + private readonly IContainer components = null; + + private GroupBox ConfigurationGroupBox; + private Label PortLabel; + private TextBox PortTextBox; + private Label RemarkLabel; + protected TextBox RemarkTextBox; + + protected ServerForm() { - private const int ControlLineHeight = 28; - private const int InputBoxWidth = 294; + InitializeComponent(); - private readonly Dictionary> _checkActions = new(); + _checkActions.Add(RemarkTextBox, s => true); + _saveActions.Add(RemarkTextBox, s => Server.Remark = (string)s); - private readonly Dictionary> _saveActions = new(); + _checkActions.Add(AddressTextBox, s => s != string.Empty); + _saveActions.Add(AddressTextBox, s => Server.Hostname = (string)s); - private int _controlLines = 2; - private Label AddressLabel; - protected TextBox AddressTextBox; + _checkActions.Add(PortTextBox, s => ushort.TryParse(s, out var port) && port != 0); + _saveActions.Add(PortTextBox, s => Server.Port = ushort.Parse((string)s)); + } - private readonly IContainer components = null; + protected abstract string TypeName { get; } - private GroupBox ConfigurationGroupBox; - private Label PortLabel; - private TextBox PortTextBox; - private Label RemarkLabel; - protected TextBox RemarkTextBox; + protected Server Server { get; set; } - protected ServerForm() + public new void ShowDialog() + { + AfterFactor(); + base.ShowDialog(); + } + + public new void Show() + { + AfterFactor(); + base.Show(); + } + + private void AfterFactor() + { + Text = TypeName ?? string.Empty; + + RemarkTextBox.Text = Server.Remark; + AddressTextBox.Text = Server.Hostname; + PortTextBox.Text = Server.Port.ToString(); + + AddSaveButton(); + i18N.TranslateForm(this); + + ConfigurationGroupBox.Enabled = !Server.IsInGroup(); + + ConfigurationGroupBox.ResumeLayout(false); + ConfigurationGroupBox.PerformLayout(); + ResumeLayout(false); + PerformLayout(); + } + + protected void CreateTextBox(string name, + string remark, + Func check, + Action save, + string value, + int width = InputBoxWidth) + { + _controlLines++; + + var textBox = new TextBox { - InitializeComponent(); + Location = new Point(120, ControlLineHeight * _controlLines), + Name = $"{name}TextBox", + Size = new Size(width, 23), + TextAlign = HorizontalAlignment.Center, + Text = value + }; - _checkActions.Add(RemarkTextBox, s => true); - _saveActions.Add(RemarkTextBox, s => Server.Remark = (string)s); - - _checkActions.Add(AddressTextBox, s => s != string.Empty); - _saveActions.Add(AddressTextBox, s => Server.Hostname = (string)s); - - _checkActions.Add(PortTextBox, s => ushort.TryParse(s, out var port) && port != 0); - _saveActions.Add(PortTextBox, s => Server.Port = ushort.Parse((string)s)); - } - - protected abstract string TypeName { get; } - - protected Server Server { get; set; } - - public new void ShowDialog() + _checkActions.Add(textBox, check); + _saveActions.Add(textBox, o => save.Invoke((string)o)); + ConfigurationGroupBox.Controls.AddRange(new Control[] { - AfterFactor(); - base.ShowDialog(); - } - - public new void Show() - { - AfterFactor(); - base.Show(); - } - - private void AfterFactor() - { - Text = TypeName ?? string.Empty; - - RemarkTextBox.Text = Server.Remark; - AddressTextBox.Text = Server.Hostname; - PortTextBox.Text = Server.Port.ToString(); - - AddSaveButton(); - i18N.TranslateForm(this); - - ConfigurationGroupBox.Enabled = !Server.IsInGroup(); - - ConfigurationGroupBox.ResumeLayout(false); - ConfigurationGroupBox.PerformLayout(); - ResumeLayout(false); - PerformLayout(); - } - - protected void CreateTextBox(string name, - string remark, - Func check, - Action save, - string value, - int width = InputBoxWidth) - { - _controlLines++; - - var textBox = new TextBox - { - Location = new Point(120, ControlLineHeight * _controlLines), - Name = $"{name}TextBox", - Size = new Size(width, 23), - TextAlign = HorizontalAlignment.Center, - Text = value - }; - - _checkActions.Add(textBox, check); - _saveActions.Add(textBox, o => save.Invoke((string)o)); - ConfigurationGroupBox.Controls.AddRange(new Control[] - { - textBox, - new Label - { - AutoSize = true, - Location = new Point(10, ControlLineHeight * _controlLines), - Name = $"{name}Label", - Size = new Size(56, 17), - Text = remark - } - }); - } - - protected void CreateComboBox(string name, string remark, List values, Action save, string value, int width = InputBoxWidth) - { - _controlLines++; - - var comboBox = new ComboBox - { - Location = new Point(120, ControlLineHeight * _controlLines), - Name = $"{name}ComboBox", - Size = new Size(width, 23), - DrawMode = DrawMode.OwnerDrawFixed, - DropDownStyle = ComboBoxStyle.DropDownList, - FormattingEnabled = true - }; - - comboBox.Items.AddRange(values.ToArray()); - comboBox.SelectedIndex = values.IndexOf(value); - comboBox.DrawItem += Utils.Utils.DrawCenterComboBox; - _saveActions.Add(comboBox, o => save.Invoke((string)o)); - ConfigurationGroupBox.Controls.AddRange(new Control[] - { - comboBox, - new Label - { - AutoSize = true, - Location = new Point(10, ControlLineHeight * _controlLines), - Name = $"{name}Label", - Size = new Size(56, 17), - Text = remark - } - }); - } - - protected void CreateCheckBox(string name, string remark, Action save, bool value) - { - _controlLines++; - - var checkBox = new CheckBox + textBox, + new Label { AutoSize = true, - Location = new Point(120, ControlLineHeight * _controlLines), - Name = $"{name}CheckBox", - Checked = value, + Location = new Point(10, ControlLineHeight * _controlLines), + Name = $"{name}Label", + Size = new Size(56, 17), Text = remark - }; + } + }); + } - _saveActions.Add(checkBox, o => save.Invoke((bool)o)); - ConfigurationGroupBox.Controls.AddRange(new Control[] + protected void CreateComboBox(string name, string remark, List values, Action save, string value, int width = InputBoxWidth) + { + _controlLines++; + + var comboBox = new ComboBox + { + Location = new Point(120, ControlLineHeight * _controlLines), + Name = $"{name}ComboBox", + Size = new Size(width, 23), + DrawMode = DrawMode.OwnerDrawFixed, + DropDownStyle = ComboBoxStyle.DropDownList, + FormattingEnabled = true + }; + + comboBox.Items.AddRange(values.ToArray()); + comboBox.SelectedIndex = values.IndexOf(value); + comboBox.DrawItem += Utils.Utils.DrawCenterComboBox; + _saveActions.Add(comboBox, o => save.Invoke((string)o)); + ConfigurationGroupBox.Controls.AddRange(new Control[] + { + comboBox, + new Label { - checkBox - }); + AutoSize = true, + Location = new Point(10, ControlLineHeight * _controlLines), + Name = $"{name}Label", + Size = new Size(56, 17), + Text = remark + } + }); + } + + protected void CreateCheckBox(string name, string remark, Action save, bool value) + { + _controlLines++; + + var checkBox = new CheckBox + { + AutoSize = true, + Location = new Point(120, ControlLineHeight * _controlLines), + Name = $"{name}CheckBox", + Checked = value, + Text = remark + }; + + _saveActions.Add(checkBox, o => save.Invoke((bool)o)); + ConfigurationGroupBox.Controls.AddRange(new Control[] + { + checkBox + }); + } + + private void AddSaveButton() + { + _controlLines++; + var control = new Button + { + Location = new Point(340, _controlLines * ControlLineHeight + 10), + Name = "ControlButton", + Size = new Size(75, 23), + Text = "Save", + UseVisualStyleBackColor = true + }; + + control.Click += ControlButton_Click; + ConfigurationGroupBox.Controls.Add(control); + } + + private void ControlButton_Click(object sender, EventArgs e) + { + Utils.Utils.ComponentIterator(this, component => Utils.Utils.ChangeControlForeColor(component, Color.Black)); + + var flag = true; + foreach (var pair in _checkActions.Where(pair => !pair.Value.Invoke(pair.Key.Text))) + { + Utils.Utils.ChangeControlForeColor(pair.Key, Color.Red); + flag = false; } - private void AddSaveButton() - { - _controlLines++; - var control = new Button + if (!flag) + return; + + foreach (var pair in _saveActions) + switch (pair.Key) { - Location = new Point(340, _controlLines * ControlLineHeight + 10), - Name = "ControlButton", - Size = new Size(75, 23), - Text = "Save", - UseVisualStyleBackColor = true - }; - - control.Click += ControlButton_Click; - ConfigurationGroupBox.Controls.Add(control); - } - - private void ControlButton_Click(object sender, EventArgs e) - { - Utils.Utils.ComponentIterator(this, component => Utils.Utils.ChangeControlForeColor(component, Color.Black)); - - var flag = true; - foreach (var pair in _checkActions.Where(pair => !pair.Value.Invoke(pair.Key.Text))) - { - Utils.Utils.ChangeControlForeColor(pair.Key, Color.Red); - flag = false; + case CheckBox c: + pair.Value.Invoke(c.Checked); + break; + default: + pair.Value.Invoke(pair.Key.Text); + break; } - if (!flag) - return; + if (Global.Settings.Server.IndexOf(Server) == -1) + Global.Settings.Server.Add(Server); - foreach (var pair in _saveActions) - switch (pair.Key) - { - case CheckBox c: - pair.Value.Invoke(c.Checked); - break; - default: - pair.Value.Invoke(pair.Key.Text); - break; - } + MessageBoxX.Show(i18N.Translate("Saved")); - if (Global.Settings.Server.IndexOf(Server) == -1) - Global.Settings.Server.Add(Server); + Close(); + } - MessageBoxX.Show(i18N.Translate("Saved")); + protected override void Dispose(bool disposing) + { + if (disposing) + components?.Dispose(); - Close(); - } + base.Dispose(disposing); + } - protected override void Dispose(bool disposing) - { - if (disposing) - components?.Dispose(); - - base.Dispose(disposing); - } - - private void InitializeComponent() - { - ConfigurationGroupBox = new GroupBox(); - AddressLabel = new Label(); - PortTextBox = new TextBox(); - AddressTextBox = new TextBox(); - RemarkTextBox = new TextBox(); - RemarkLabel = new Label(); - PortLabel = new Label(); - ConfigurationGroupBox.SuspendLayout(); - SuspendLayout(); - // - // ConfigurationGroupBox - // - ConfigurationGroupBox.AutoSize = true; - ConfigurationGroupBox.AutoSizeMode = AutoSizeMode.GrowAndShrink; - ConfigurationGroupBox.Controls.Add(AddressLabel); - ConfigurationGroupBox.Controls.Add(PortTextBox); - ConfigurationGroupBox.Controls.Add(AddressTextBox); - ConfigurationGroupBox.Controls.Add(RemarkTextBox); - ConfigurationGroupBox.Controls.Add(RemarkLabel); - ConfigurationGroupBox.Controls.Add(PortLabel); - ConfigurationGroupBox.Dock = DockStyle.Fill; - ConfigurationGroupBox.Location = new Point(5, 5); - ConfigurationGroupBox.Name = "ConfigurationGroupBox"; - ConfigurationGroupBox.Size = new Size(434, 127); - ConfigurationGroupBox.TabIndex = 0; - ConfigurationGroupBox.TabStop = false; - ConfigurationGroupBox.Text = "Configuration"; - // - // AddressLabel - // - AddressLabel.AutoSize = true; - AddressLabel.Location = new Point(10, ControlLineHeight * 2); - AddressLabel.Name = "AddressLabel"; - AddressLabel.Size = new Size(56, 17); - AddressLabel.TabIndex = 2; - AddressLabel.Text = "Address"; - // - // PortTextBox - // - PortTextBox.Location = new Point(358, ControlLineHeight * 2); - PortTextBox.Name = "PortTextBox"; - PortTextBox.Size = new Size(56, 23); - PortTextBox.TabIndex = 5; - PortTextBox.TextAlign = HorizontalAlignment.Center; - // - // AddressTextBox - // - AddressTextBox.Location = new Point(120, ControlLineHeight * 2); - AddressTextBox.Name = "AddressTextBox"; - AddressTextBox.Size = new Size(232, 23); - AddressTextBox.TabIndex = 3; - AddressTextBox.TextAlign = HorizontalAlignment.Center; - // - // RemarkTextBox - // - RemarkTextBox.Location = new Point(120, ControlLineHeight); - RemarkTextBox.Name = "RemarkTextBox"; - RemarkTextBox.Size = new Size(294, 23); - RemarkTextBox.TabIndex = 1; - RemarkTextBox.TextAlign = HorizontalAlignment.Center; - // - // RemarkLabel - // - RemarkLabel.AutoSize = true; - RemarkLabel.Location = new Point(10, ControlLineHeight); - RemarkLabel.Name = "RemarkLabel"; - RemarkLabel.Size = new Size(53, 17); - RemarkLabel.TabIndex = 0; - RemarkLabel.Text = "Remark"; - // - // PortLabel - // - PortLabel.AutoSize = true; - PortLabel.Location = new Point(351, ControlLineHeight * 2); - PortLabel.Name = "PortLabel"; - PortLabel.Size = new Size(11, 17); - PortLabel.TabIndex = 4; - PortLabel.Text = ":"; - // - // ServerForm - // - AutoScaleDimensions = new SizeF(96F, 96F); - AutoScaleMode = AutoScaleMode.Dpi; - AutoSize = true; - AutoSizeMode = AutoSizeMode.GrowAndShrink; - ClientSize = new Size(444, 137); - Controls.Add(ConfigurationGroupBox); - Font = new Font("微软雅黑", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); - FormBorderStyle = FormBorderStyle.FixedSingle; - Icon = Icon.FromHandle(Resources.Netch.GetHicon()); - Margin = new Padding(3, 4, 3, 4); - MaximizeBox = false; - Name = "ServerForm"; - Padding = new Padding(11, 5, 11, 4); - StartPosition = FormStartPosition.CenterScreen; - } + private void InitializeComponent() + { + ConfigurationGroupBox = new GroupBox(); + AddressLabel = new Label(); + PortTextBox = new TextBox(); + AddressTextBox = new TextBox(); + RemarkTextBox = new TextBox(); + RemarkLabel = new Label(); + PortLabel = new Label(); + ConfigurationGroupBox.SuspendLayout(); + SuspendLayout(); + // + // ConfigurationGroupBox + // + ConfigurationGroupBox.AutoSize = true; + ConfigurationGroupBox.AutoSizeMode = AutoSizeMode.GrowAndShrink; + ConfigurationGroupBox.Controls.Add(AddressLabel); + ConfigurationGroupBox.Controls.Add(PortTextBox); + ConfigurationGroupBox.Controls.Add(AddressTextBox); + ConfigurationGroupBox.Controls.Add(RemarkTextBox); + ConfigurationGroupBox.Controls.Add(RemarkLabel); + ConfigurationGroupBox.Controls.Add(PortLabel); + ConfigurationGroupBox.Dock = DockStyle.Fill; + ConfigurationGroupBox.Location = new Point(5, 5); + ConfigurationGroupBox.Name = "ConfigurationGroupBox"; + ConfigurationGroupBox.Size = new Size(434, 127); + ConfigurationGroupBox.TabIndex = 0; + ConfigurationGroupBox.TabStop = false; + ConfigurationGroupBox.Text = "Configuration"; + // + // AddressLabel + // + AddressLabel.AutoSize = true; + AddressLabel.Location = new Point(10, ControlLineHeight * 2); + AddressLabel.Name = "AddressLabel"; + AddressLabel.Size = new Size(56, 17); + AddressLabel.TabIndex = 2; + AddressLabel.Text = "Address"; + // + // PortTextBox + // + PortTextBox.Location = new Point(358, ControlLineHeight * 2); + PortTextBox.Name = "PortTextBox"; + PortTextBox.Size = new Size(56, 23); + PortTextBox.TabIndex = 5; + PortTextBox.TextAlign = HorizontalAlignment.Center; + // + // AddressTextBox + // + AddressTextBox.Location = new Point(120, ControlLineHeight * 2); + AddressTextBox.Name = "AddressTextBox"; + AddressTextBox.Size = new Size(232, 23); + AddressTextBox.TabIndex = 3; + AddressTextBox.TextAlign = HorizontalAlignment.Center; + // + // RemarkTextBox + // + RemarkTextBox.Location = new Point(120, ControlLineHeight); + RemarkTextBox.Name = "RemarkTextBox"; + RemarkTextBox.Size = new Size(294, 23); + RemarkTextBox.TabIndex = 1; + RemarkTextBox.TextAlign = HorizontalAlignment.Center; + // + // RemarkLabel + // + RemarkLabel.AutoSize = true; + RemarkLabel.Location = new Point(10, ControlLineHeight); + RemarkLabel.Name = "RemarkLabel"; + RemarkLabel.Size = new Size(53, 17); + RemarkLabel.TabIndex = 0; + RemarkLabel.Text = "Remark"; + // + // PortLabel + // + PortLabel.AutoSize = true; + PortLabel.Location = new Point(351, ControlLineHeight * 2); + PortLabel.Name = "PortLabel"; + PortLabel.Size = new Size(11, 17); + PortLabel.TabIndex = 4; + PortLabel.Text = ":"; + // + // ServerForm + // + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + AutoSize = true; + AutoSizeMode = AutoSizeMode.GrowAndShrink; + ClientSize = new Size(444, 137); + Controls.Add(ConfigurationGroupBox); + Font = new Font("微软雅黑", 9F, FontStyle.Regular, GraphicsUnit.Point, 134); + FormBorderStyle = FormBorderStyle.FixedSingle; + Icon = Icon.FromHandle(Resources.Netch.GetHicon()); + Margin = new Padding(3, 4, 3, 4); + MaximizeBox = false; + Name = "ServerForm"; + Padding = new Padding(11, 5, 11, 4); + StartPosition = FormStartPosition.CenterScreen; } } \ No newline at end of file diff --git a/Netch/Forms/SettingForm.cs b/Netch/Forms/SettingForm.cs index 1c05eefb..da34a76a 100644 --- a/Netch/Forms/SettingForm.cs +++ b/Netch/Forms/SettingForm.cs @@ -1,259 +1,252 @@ -using System; -using System.Drawing; -using System.IO; -using System.Linq; using System.Net; -using System.Windows.Forms; using Netch.Properties; using Netch.Utils; -using Serilog; -namespace Netch.Forms +namespace Netch.Forms; + +public partial class SettingForm : BindingForm { - public partial class SettingForm : BindingForm + public SettingForm() { - public SettingForm() + InitializeComponent(); + Icon = Resources.icon; + i18N.TranslateForm(this); + + #region General + + BindTextBox(Socks5PortTextBox, p => true, p => Global.Settings.Socks5LocalPort = p, Global.Settings.Socks5LocalPort); + + BindCheckBox(AllowDevicesCheckBox, + c => Global.Settings.LocalAddress = AllowDevicesCheckBox.Checked ? "0.0.0.0" : "127.0.0.1", + Global.Settings.LocalAddress switch { "127.0.0.1" => false, "0.0.0.0" => true, _ => false }); + + BindRadioBox(ICMPingRadioBtn, _ => { }, !Global.Settings.ServerTCPing); + + BindRadioBox(TCPingRadioBtn, c => Global.Settings.ServerTCPing = c, Global.Settings.ServerTCPing); + + BindTextBox(ProfileCountTextBox, i => i > -1, i => Global.Settings.ProfileCount = i, Global.Settings.ProfileCount); + BindTextBox(DetectionTickTextBox, + i => DelayTestHelper.Range.InRange(i), + i => Global.Settings.DetectionTick = i, + Global.Settings.DetectionTick); + + BindTextBox(StartedPingIntervalTextBox, + _ => true, + i => Global.Settings.StartedPingInterval = i, + Global.Settings.StartedPingInterval); + + object[]? stuns; + try { - InitializeComponent(); - Icon = Resources.icon; - i18N.TranslateForm(this); + stuns = File.ReadLines(Constants.STUNServersFile).Cast().ToArray(); + } + catch (Exception e) + { + Log.Warning(e, "Load stun.txt failed"); + stuns = null; + } - #region General - - BindTextBox(Socks5PortTextBox, p => true, p => Global.Settings.Socks5LocalPort = p, Global.Settings.Socks5LocalPort); - - BindCheckBox(AllowDevicesCheckBox, - c => Global.Settings.LocalAddress = AllowDevicesCheckBox.Checked ? "0.0.0.0" : "127.0.0.1", - Global.Settings.LocalAddress switch { "127.0.0.1" => false, "0.0.0.0" => true, _ => false }); - - BindRadioBox(ICMPingRadioBtn, _ => { }, !Global.Settings.ServerTCPing); - - BindRadioBox(TCPingRadioBtn, c => Global.Settings.ServerTCPing = c, Global.Settings.ServerTCPing); - - BindTextBox(ProfileCountTextBox, i => i > -1, i => Global.Settings.ProfileCount = i, Global.Settings.ProfileCount); - BindTextBox(DetectionTickTextBox, - i => DelayTestHelper.Range.InRange(i), - i => Global.Settings.DetectionTick = i, - Global.Settings.DetectionTick); - - BindTextBox(StartedPingIntervalTextBox, - _ => true, - i => Global.Settings.StartedPingInterval = i, - Global.Settings.StartedPingInterval); - - object[]? stuns; - try + BindComboBox(STUN_ServerComboBox, + s => { - stuns = File.ReadLines(Constants.STUNServersFile).Cast().ToArray(); - } - catch (Exception e) - { - Log.Warning(e, "Load stun.txt failed"); - stuns = null; - } + var split = s.SplitRemoveEmptyEntriesAndTrimEntries(':'); + if (!split.Any()) + return false; - BindComboBox(STUN_ServerComboBox, - s => - { - var split = s.SplitRemoveEmptyEntriesAndTrimEntries(':'); - if (!split.Any()) + var port = split.ElementAtOrDefault(1); + if (port != null) + if (!ushort.TryParse(split[1], out _)) return false; - var port = split.ElementAtOrDefault(1); - if (port != null) - if (!ushort.TryParse(split[1], out _)) - return false; + return true; + }, + o => + { + var split = o.ToString().SplitRemoveEmptyEntriesAndTrimEntries(':'); + Global.Settings.STUN_Server = split[0]; - return true; - }, - o => - { - var split = o.ToString().SplitRemoveEmptyEntriesAndTrimEntries(':'); - Global.Settings.STUN_Server = split[0]; + var port = split.ElementAtOrDefault(1); + Global.Settings.STUN_Server_Port = port != null ? ushort.Parse(port) : 3478; + }, + Global.Settings.STUN_Server + ":" + Global.Settings.STUN_Server_Port, + stuns); - var port = split.ElementAtOrDefault(1); - Global.Settings.STUN_Server_Port = port != null ? ushort.Parse(port) : 3478; - }, - Global.Settings.STUN_Server + ":" + Global.Settings.STUN_Server_Port, - stuns); + BindListComboBox(LanguageComboBox, o => Global.Settings.Language = o.ToString(), i18N.GetTranslateList(), Global.Settings.Language); - BindListComboBox(LanguageComboBox, o => Global.Settings.Language = o.ToString(), i18N.GetTranslateList(), Global.Settings.Language); + #endregion - #endregion + #region Process Mode - #region Process Mode + BindCheckBox(FilterTCPCheckBox, b => Global.Settings.Redirector.FilterTCP = b, Global.Settings.Redirector.FilterTCP); - BindCheckBox(FilterTCPCheckBox, b => Global.Settings.Redirector.FilterTCP = b, Global.Settings.Redirector.FilterTCP); + BindCheckBox(FilterUDPCheckBox, b => Global.Settings.Redirector.FilterUDP = b, Global.Settings.Redirector.FilterUDP); - BindCheckBox(FilterUDPCheckBox, b => Global.Settings.Redirector.FilterUDP = b, Global.Settings.Redirector.FilterUDP); + BindCheckBox(FilterICMPCheckBox, b => Global.Settings.Redirector.FilterICMP = b, Global.Settings.Redirector.FilterICMP); - BindCheckBox(FilterICMPCheckBox, b => Global.Settings.Redirector.FilterICMP = b, Global.Settings.Redirector.FilterICMP); + BindTextBox(ICMPDelayTextBox, s => true, s => Global.Settings.Redirector.ICMPDelay = s, Global.Settings.Redirector.ICMPDelay); - BindTextBox(ICMPDelayTextBox, s => true, s => Global.Settings.Redirector.ICMPDelay = s, Global.Settings.Redirector.ICMPDelay); + BindCheckBox(FilterDNSCheckBox, b => Global.Settings.Redirector.FilterDNS = b, Global.Settings.Redirector.FilterDNS); - BindCheckBox(FilterDNSCheckBox, b => Global.Settings.Redirector.FilterDNS = b, Global.Settings.Redirector.FilterDNS); + BindTextBox(DNSHijackHostTextBox, s => true, s => Global.Settings.Redirector.DNSHost = s, Global.Settings.Redirector.DNSHost); - BindTextBox(DNSHijackHostTextBox, s => true, s => Global.Settings.Redirector.DNSHost = s, Global.Settings.Redirector.DNSHost); + BindCheckBox(ChildProcessHandleCheckBox, + s => Global.Settings.Redirector.FilterParent = s, + Global.Settings.Redirector.FilterParent); - BindCheckBox(ChildProcessHandleCheckBox, - s => Global.Settings.Redirector.FilterParent = s, - Global.Settings.Redirector.FilterParent); + BindCheckBox(DNSProxyCheckBox, b => Global.Settings.Redirector.DNSProxy = b, Global.Settings.Redirector.DNSProxy); - BindCheckBox(DNSProxyCheckBox, b => Global.Settings.Redirector.DNSProxy = b, Global.Settings.Redirector.DNSProxy); + BindCheckBox(HandleProcDNSCheckBox, b => Global.Settings.Redirector.HandleOnlyDNS = b, Global.Settings.Redirector.HandleOnlyDNS); - BindCheckBox(HandleProcDNSCheckBox, b => Global.Settings.Redirector.HandleOnlyDNS = b, Global.Settings.Redirector.HandleOnlyDNS); + #endregion - #endregion + #region TUN/TAP - #region TUN/TAP + BindTextBox(TUNTAPAddressTextBox, + s => IPAddress.TryParse(s, out _), + s => Global.Settings.TUNTAP.Address = s, + Global.Settings.TUNTAP.Address); - BindTextBox(TUNTAPAddressTextBox, - s => IPAddress.TryParse(s, out _), - s => Global.Settings.TUNTAP.Address = s, - Global.Settings.TUNTAP.Address); + BindTextBox(TUNTAPNetmaskTextBox, + s => IPAddress.TryParse(s, out _), + s => Global.Settings.TUNTAP.Netmask = s, + Global.Settings.TUNTAP.Netmask); - BindTextBox(TUNTAPNetmaskTextBox, - s => IPAddress.TryParse(s, out _), - s => Global.Settings.TUNTAP.Netmask = s, - Global.Settings.TUNTAP.Netmask); + BindTextBox(TUNTAPGatewayTextBox, + s => IPAddress.TryParse(s, out _), + s => Global.Settings.TUNTAP.Gateway = s, + Global.Settings.TUNTAP.Gateway); - BindTextBox(TUNTAPGatewayTextBox, - s => IPAddress.TryParse(s, out _), - s => Global.Settings.TUNTAP.Gateway = s, - Global.Settings.TUNTAP.Gateway); + BindCheckBox(UseCustomDNSCheckBox, b => { Global.Settings.TUNTAP.UseCustomDNS = b; }, Global.Settings.TUNTAP.UseCustomDNS); - BindCheckBox(UseCustomDNSCheckBox, b => { Global.Settings.TUNTAP.UseCustomDNS = b; }, Global.Settings.TUNTAP.UseCustomDNS); + BindTextBox(TUNTAPDNSTextBox, + _ => true, + s => + { + if (UseCustomDNSCheckBox.Checked) + Global.Settings.TUNTAP.DNS = s; + }, + Global.Settings.TUNTAP.DNS); - BindTextBox(TUNTAPDNSTextBox, - _ => true, - s => - { - if (UseCustomDNSCheckBox.Checked) - Global.Settings.TUNTAP.DNS = s; - }, - Global.Settings.TUNTAP.DNS); + BindCheckBox(ProxyDNSCheckBox, b => Global.Settings.TUNTAP.ProxyDNS = b, Global.Settings.TUNTAP.ProxyDNS); - BindCheckBox(ProxyDNSCheckBox, b => Global.Settings.TUNTAP.ProxyDNS = b, Global.Settings.TUNTAP.ProxyDNS); + #endregion - #endregion + #region V2Ray - #region V2Ray + BindCheckBox(XrayConeCheckBox, b => Global.Settings.V2RayConfig.XrayCone = b, Global.Settings.V2RayConfig.XrayCone); - BindCheckBox(XrayConeCheckBox, b => Global.Settings.V2RayConfig.XrayCone = b, Global.Settings.V2RayConfig.XrayCone); + BindCheckBox(TLSAllowInsecureCheckBox, b => Global.Settings.V2RayConfig.AllowInsecure = b, Global.Settings.V2RayConfig.AllowInsecure); + BindCheckBox(UseMuxCheckBox, b => Global.Settings.V2RayConfig.UseMux = b, Global.Settings.V2RayConfig.UseMux); - BindCheckBox(TLSAllowInsecureCheckBox, b => Global.Settings.V2RayConfig.AllowInsecure = b, Global.Settings.V2RayConfig.AllowInsecure); - BindCheckBox(UseMuxCheckBox, b => Global.Settings.V2RayConfig.UseMux = b, Global.Settings.V2RayConfig.UseMux); + BindTextBox(mtuTextBox, i => true, i => Global.Settings.V2RayConfig.KcpConfig.mtu = i, Global.Settings.V2RayConfig.KcpConfig.mtu); + BindTextBox(ttiTextBox, i => true, i => Global.Settings.V2RayConfig.KcpConfig.tti = i, Global.Settings.V2RayConfig.KcpConfig.tti); + BindTextBox(uplinkCapacityTextBox, + i => true, + i => Global.Settings.V2RayConfig.KcpConfig.uplinkCapacity = i, + Global.Settings.V2RayConfig.KcpConfig.uplinkCapacity); - BindTextBox(mtuTextBox, i => true, i => Global.Settings.V2RayConfig.KcpConfig.mtu = i, Global.Settings.V2RayConfig.KcpConfig.mtu); - BindTextBox(ttiTextBox, i => true, i => Global.Settings.V2RayConfig.KcpConfig.tti = i, Global.Settings.V2RayConfig.KcpConfig.tti); - BindTextBox(uplinkCapacityTextBox, - i => true, - i => Global.Settings.V2RayConfig.KcpConfig.uplinkCapacity = i, - Global.Settings.V2RayConfig.KcpConfig.uplinkCapacity); + BindTextBox(downlinkCapacityTextBox, + i => true, + i => Global.Settings.V2RayConfig.KcpConfig.downlinkCapacity = i, + Global.Settings.V2RayConfig.KcpConfig.downlinkCapacity); - BindTextBox(downlinkCapacityTextBox, - i => true, - i => Global.Settings.V2RayConfig.KcpConfig.downlinkCapacity = i, - Global.Settings.V2RayConfig.KcpConfig.downlinkCapacity); + BindTextBox(readBufferSizeTextBox, + i => true, + i => Global.Settings.V2RayConfig.KcpConfig.readBufferSize = i, + Global.Settings.V2RayConfig.KcpConfig.readBufferSize); - BindTextBox(readBufferSizeTextBox, - i => true, - i => Global.Settings.V2RayConfig.KcpConfig.readBufferSize = i, - Global.Settings.V2RayConfig.KcpConfig.readBufferSize); + BindTextBox(writeBufferSizeTextBox, + i => true, + i => Global.Settings.V2RayConfig.KcpConfig.writeBufferSize = i, + Global.Settings.V2RayConfig.KcpConfig.writeBufferSize); - BindTextBox(writeBufferSizeTextBox, - i => true, - i => Global.Settings.V2RayConfig.KcpConfig.writeBufferSize = i, - Global.Settings.V2RayConfig.KcpConfig.writeBufferSize); + BindCheckBox(congestionCheckBox, + b => Global.Settings.V2RayConfig.KcpConfig.congestion = b, + Global.Settings.V2RayConfig.KcpConfig.congestion); - BindCheckBox(congestionCheckBox, - b => Global.Settings.V2RayConfig.KcpConfig.congestion = b, - Global.Settings.V2RayConfig.KcpConfig.congestion); + #endregion - #endregion + #region Others - #region Others + BindCheckBox(ExitWhenClosedCheckBox, b => Global.Settings.ExitWhenClosed = b, Global.Settings.ExitWhenClosed); - BindCheckBox(ExitWhenClosedCheckBox, b => Global.Settings.ExitWhenClosed = b, Global.Settings.ExitWhenClosed); + BindCheckBox(StopWhenExitedCheckBox, b => Global.Settings.StopWhenExited = b, Global.Settings.StopWhenExited); - BindCheckBox(StopWhenExitedCheckBox, b => Global.Settings.StopWhenExited = b, Global.Settings.StopWhenExited); + BindCheckBox(StartWhenOpenedCheckBox, b => Global.Settings.StartWhenOpened = b, Global.Settings.StartWhenOpened); - BindCheckBox(StartWhenOpenedCheckBox, b => Global.Settings.StartWhenOpened = b, Global.Settings.StartWhenOpened); + BindCheckBox(MinimizeWhenStartedCheckBox, b => Global.Settings.MinimizeWhenStarted = b, Global.Settings.MinimizeWhenStarted); - BindCheckBox(MinimizeWhenStartedCheckBox, b => Global.Settings.MinimizeWhenStarted = b, Global.Settings.MinimizeWhenStarted); + BindCheckBox(RunAtStartupCheckBox, b => Global.Settings.RunAtStartup = b, Global.Settings.RunAtStartup); - BindCheckBox(RunAtStartupCheckBox, b => Global.Settings.RunAtStartup = b, Global.Settings.RunAtStartup); + BindCheckBox(CheckUpdateWhenOpenedCheckBox, b => Global.Settings.CheckUpdateWhenOpened = b, Global.Settings.CheckUpdateWhenOpened); - BindCheckBox(CheckUpdateWhenOpenedCheckBox, b => Global.Settings.CheckUpdateWhenOpened = b, Global.Settings.CheckUpdateWhenOpened); + BindCheckBox(CheckBetaUpdateCheckBox, b => Global.Settings.CheckBetaUpdate = b, Global.Settings.CheckBetaUpdate); - BindCheckBox(CheckBetaUpdateCheckBox, b => Global.Settings.CheckBetaUpdate = b, Global.Settings.CheckBetaUpdate); + BindCheckBox(UpdateServersWhenOpenedCheckBox, b => Global.Settings.UpdateServersWhenOpened = b, Global.Settings.UpdateServersWhenOpened); - BindCheckBox(UpdateServersWhenOpenedCheckBox, b => Global.Settings.UpdateServersWhenOpened = b, Global.Settings.UpdateServersWhenOpened); + BindCheckBox(NoSupportDialogCheckBox, b => Global.Settings.NoSupportDialog = b, Global.Settings.NoSupportDialog); - BindCheckBox(NoSupportDialogCheckBox, b => Global.Settings.NoSupportDialog = b, Global.Settings.NoSupportDialog); + #endregion - #endregion + #region AioDNS - #region AioDNS + BindTextBox(ChinaDNSTextBox, _ => true, s => Global.Settings.AioDNS.ChinaDNS = s, Global.Settings.AioDNS.ChinaDNS); - BindTextBox(ChinaDNSTextBox, _ => true, s => Global.Settings.AioDNS.ChinaDNS = s, Global.Settings.AioDNS.ChinaDNS); + BindTextBox(OtherDNSTextBox, _ => true, s => Global.Settings.AioDNS.OtherDNS = s, Global.Settings.AioDNS.OtherDNS); - BindTextBox(OtherDNSTextBox, _ => true, s => Global.Settings.AioDNS.OtherDNS = s, Global.Settings.AioDNS.OtherDNS); + BindTextBox(AioDNSListenPortTextBox, + s => ushort.TryParse(s, out _), + s => Global.Settings.AioDNS.ListenPort = ushort.Parse(s), + Global.Settings.AioDNS.ListenPort); - BindTextBox(AioDNSListenPortTextBox, - s => ushort.TryParse(s, out _), - s => Global.Settings.AioDNS.ListenPort = ushort.Parse(s), - Global.Settings.AioDNS.ListenPort); + #endregion + } - #endregion - } + private void SettingForm_Load(object sender, EventArgs e) + { + TUNTAPUseCustomDNSCheckBox_CheckedChanged(null, null); + } - private void SettingForm_Load(object sender, EventArgs e) - { - TUNTAPUseCustomDNSCheckBox_CheckedChanged(null, null); - } + private void TUNTAPUseCustomDNSCheckBox_CheckedChanged(object? sender, EventArgs? e) + { + if (UseCustomDNSCheckBox.Checked) + TUNTAPDNSTextBox.Text = Global.Settings.TUNTAP.DNS; + else + TUNTAPDNSTextBox.Text = "AioDNS"; + } - private void TUNTAPUseCustomDNSCheckBox_CheckedChanged(object? sender, EventArgs? e) - { - if (UseCustomDNSCheckBox.Checked) - TUNTAPDNSTextBox.Text = Global.Settings.TUNTAP.DNS; - else - TUNTAPDNSTextBox.Text = "AioDNS"; - } + private void GlobalBypassIPsButton_Click(object sender, EventArgs e) + { + Hide(); + new GlobalBypassIPForm().ShowDialog(); + Show(); + } - private void GlobalBypassIPsButton_Click(object sender, EventArgs e) - { - Hide(); - new GlobalBypassIPForm().ShowDialog(); - Show(); - } + private async void ControlButton_Click(object sender, EventArgs e) + { + Utils.Utils.ComponentIterator(this, component => Utils.Utils.ChangeControlForeColor(component, Color.Black)); - private async void ControlButton_Click(object sender, EventArgs e) - { - Utils.Utils.ComponentIterator(this, component => Utils.Utils.ChangeControlForeColor(component, Color.Black)); + #region Check - #region Check + var checkNotPassControl = GetCheckFailedControls(); + foreach (Control control in checkNotPassControl) + Utils.Utils.ChangeControlForeColor(control, Color.Red); - var checkNotPassControl = GetCheckFailedControls(); - foreach (Control control in checkNotPassControl) - Utils.Utils.ChangeControlForeColor(control, Color.Red); + if (checkNotPassControl.Any()) + return; - if (checkNotPassControl.Any()) - return; + #endregion - #endregion + #region Save - #region Save + SaveBinds(); - SaveBinds(); + #endregion - #endregion + Utils.Utils.RegisterNetchStartupItem(); - Utils.Utils.RegisterNetchStartupItem(); - - await Configuration.SaveAsync(); - MessageBoxX.Show(i18N.Translate("Saved")); - Close(); - } + await Configuration.SaveAsync(); + MessageBoxX.Show(i18N.Translate("Saved")); + Close(); } } \ No newline at end of file diff --git a/Netch/Forms/SubscriptionForm.cs b/Netch/Forms/SubscriptionForm.cs index fcfcc7d3..0c49f3c3 100644 --- a/Netch/Forms/SubscriptionForm.cs +++ b/Netch/Forms/SubscriptionForm.cs @@ -1,216 +1,212 @@ -using System; -using System.Linq; -using System.Windows.Forms; -using Netch.Models; +using Netch.Models; using Netch.Properties; using Netch.Utils; -namespace Netch.Forms +namespace Netch.Forms; + +public partial class SubscriptionForm : Form { - public partial class SubscriptionForm : Form + public SubscriptionForm() { - public SubscriptionForm() - { - InitializeComponent(); - Icon = Resources.icon; + InitializeComponent(); + Icon = Resources.icon; - i18N.TranslateForm(this); - i18N.TranslateForm(pContextMenuStrip); + i18N.TranslateForm(this); + i18N.TranslateForm(pContextMenuStrip); - LoadSubscriptionLinks(); - } - - private int SelectedIndex - { - get - { - if (SubscriptionLinkListView.MultiSelect) - throw new Exception(); - - return SubscriptionLinkListView.SelectedIndices.Count == 0 ? -1 : SubscriptionLinkListView.SelectedIndices[0]; - } - } - - #region EventHandler - - private void SubscriptionLinkListView_MouseUp(object sender, MouseEventArgs e) - { - if (e.Button == MouseButtons.Right) - if (SelectedIndex != -1) - pContextMenuStrip.Show(SubscriptionLinkListView, e.Location); - } - - /// - /// 选中/取消选中 - /// - private void SubscriptionLinkListView_SelectedIndexChanged(object sender, EventArgs e) - { - SetEditingGroup(SelectedIndex); - } - - /// - /// 订阅启/禁用 - /// - private void SubscriptionLinkListView_ItemChecked(object sender, ItemCheckedEventArgs e) - { - var index = e.Item.Index; - Global.Settings.Subscription[index].Enable = SubscriptionLinkListView.Items[index].Checked; - } - - private async void SubscriptionForm_FormClosing(object sender, FormClosingEventArgs e) - { - await Configuration.SaveAsync(); - } - - #endregion - - #region EditBox - - private void UnselectButton_Click(object sender, EventArgs e) - { - ResetEditingGroup(); - } - - private void AddButton_Click(object sender, EventArgs e) - { - if (string.IsNullOrWhiteSpace(RemarkTextBox.Text)) - { - MessageBoxX.Show(i18N.Translate("Remark can not be empty")); - return; - } - - if (string.IsNullOrWhiteSpace(LinkTextBox.Text)) - { - MessageBoxX.Show(i18N.Translate("Link can not be empty")); - return; - } - - if (!LinkTextBox.Text.StartsWith("HTTP://", StringComparison.OrdinalIgnoreCase) && - !LinkTextBox.Text.StartsWith("HTTPS://", StringComparison.OrdinalIgnoreCase)) - { - MessageBoxX.Show(i18N.Translate("Link must start with http:// or https://")); - return; - } - - if (SelectedIndex == -1) - { - if (Global.Settings.Subscription.Any(link => link.Remark.Equals(RemarkTextBox.Text))) - { - MessageBoxX.Show(i18N.Translate("Subscription with the specified remark already exists")); - return; - } - - Global.Settings.Subscription.Add(new Subscription - { - Enable = true, - Remark = RemarkTextBox.Text, - Link = LinkTextBox.Text, - UserAgent = UserAgentTextBox.Text - }); - } - else - { - var subscribeLink = Global.Settings.Subscription[SelectedIndex]; - - RenameServers(subscribeLink.Remark, RemarkTextBox.Text); - subscribeLink.Link = LinkTextBox.Text; - subscribeLink.Remark = RemarkTextBox.Text; - subscribeLink.UserAgent = UserAgentTextBox.Text; - } - - LoadSubscriptionLinks(); - } - - #endregion - - #region ContextMenu - - private void DeleteToolStripMenuItem_Click(object sender, EventArgs e) - { - if (MessageBoxX.Show(i18N.Translate("Delete or not ? Will clean up the corresponding group of items in the server list"), - confirm: true) != DialogResult.OK) - return; - - var subscribeLink = Global.Settings.Subscription[SelectedIndex]; - DeleteServers(subscribeLink.Remark); - Global.Settings.Subscription.Remove(subscribeLink); - - LoadSubscriptionLinks(); - } - - private void DeleteServersToolStripMenuItem_Click(object sender, EventArgs e) - { - if (MessageBoxX.Show(i18N.Translate("Confirm deletion?"), confirm: true) != DialogResult.OK) - return; - - DeleteServers(Global.Settings.Subscription[SelectedIndex].Remark); - } - - private void CopyLinkToolStripMenuItem_Click(object sender, EventArgs e) - { - Clipboard.SetText(Global.Settings.Subscription[SelectedIndex].Link); - } - - #endregion - - #region Helper - - private static void DeleteServers(string group) - { - Global.Settings.Server.RemoveAll(server => server.Group == group); - } - - private static void RenameServers(string oldGroup, string newGroup) - { - foreach (var server in Global.Settings.Server.Where(server => server.Group == oldGroup)) - server.Group = newGroup; - } - - private void LoadSubscriptionLinks() - { - SubscriptionLinkListView.Items.Clear(); - - foreach (var item in Global.Settings.Subscription) - SubscriptionLinkListView.Items.Add(new ListViewItem(new[] - { - "", - item.Remark, - item.Link, - !string.IsNullOrEmpty(item.UserAgent) ? item.UserAgent : WebUtil.DefaultUserAgent - }) - { - Checked = item.Enable - }); - - ResetEditingGroup(); - } - - private void ResetEditingGroup() - { - AddSubscriptionBox.Text = string.Empty; - RemarkTextBox.Text = string.Empty; - LinkTextBox.Text = string.Empty; - UserAgentTextBox.Text = WebUtil.DefaultUserAgent; - } - - private void SetEditingGroup(int index) - { - if (index == -1) - { - ResetEditingGroup(); - AddButton.Text = i18N.Translate("Add"); - return; - } - - var item = Global.Settings.Subscription[index]; - AddSubscriptionBox.Text = item.Remark; - RemarkTextBox.Text = item.Remark; - LinkTextBox.Text = item.Link; - UserAgentTextBox.Text = item.UserAgent; - - AddButton.Text = i18N.Translate("Modify"); - } - - #endregion + LoadSubscriptionLinks(); } + + private int SelectedIndex + { + get + { + if (SubscriptionLinkListView.MultiSelect) + throw new Exception(); + + return SubscriptionLinkListView.SelectedIndices.Count == 0 ? -1 : SubscriptionLinkListView.SelectedIndices[0]; + } + } + + #region EventHandler + + private void SubscriptionLinkListView_MouseUp(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Right) + if (SelectedIndex != -1) + pContextMenuStrip.Show(SubscriptionLinkListView, e.Location); + } + + /// + /// 选中/取消选中 + /// + private void SubscriptionLinkListView_SelectedIndexChanged(object sender, EventArgs e) + { + SetEditingGroup(SelectedIndex); + } + + /// + /// 订阅启/禁用 + /// + private void SubscriptionLinkListView_ItemChecked(object sender, ItemCheckedEventArgs e) + { + var index = e.Item.Index; + Global.Settings.Subscription[index].Enable = SubscriptionLinkListView.Items[index].Checked; + } + + private async void SubscriptionForm_FormClosing(object sender, FormClosingEventArgs e) + { + await Configuration.SaveAsync(); + } + + #endregion + + #region EditBox + + private void UnselectButton_Click(object sender, EventArgs e) + { + ResetEditingGroup(); + } + + private void AddButton_Click(object sender, EventArgs e) + { + if (string.IsNullOrWhiteSpace(RemarkTextBox.Text)) + { + MessageBoxX.Show(i18N.Translate("Remark can not be empty")); + return; + } + + if (string.IsNullOrWhiteSpace(LinkTextBox.Text)) + { + MessageBoxX.Show(i18N.Translate("Link can not be empty")); + return; + } + + if (!LinkTextBox.Text.StartsWith("HTTP://", StringComparison.OrdinalIgnoreCase) && + !LinkTextBox.Text.StartsWith("HTTPS://", StringComparison.OrdinalIgnoreCase)) + { + MessageBoxX.Show(i18N.Translate("Link must start with http:// or https://")); + return; + } + + if (SelectedIndex == -1) + { + if (Global.Settings.Subscription.Any(link => link.Remark.Equals(RemarkTextBox.Text))) + { + MessageBoxX.Show(i18N.Translate("Subscription with the specified remark already exists")); + return; + } + + Global.Settings.Subscription.Add(new Subscription + { + Enable = true, + Remark = RemarkTextBox.Text, + Link = LinkTextBox.Text, + UserAgent = UserAgentTextBox.Text + }); + } + else + { + var subscribeLink = Global.Settings.Subscription[SelectedIndex]; + + RenameServers(subscribeLink.Remark, RemarkTextBox.Text); + subscribeLink.Link = LinkTextBox.Text; + subscribeLink.Remark = RemarkTextBox.Text; + subscribeLink.UserAgent = UserAgentTextBox.Text; + } + + LoadSubscriptionLinks(); + } + + #endregion + + #region ContextMenu + + private void DeleteToolStripMenuItem_Click(object sender, EventArgs e) + { + if (MessageBoxX.Show(i18N.Translate("Delete or not ? Will clean up the corresponding group of items in the server list"), + confirm: true) != DialogResult.OK) + return; + + var subscribeLink = Global.Settings.Subscription[SelectedIndex]; + DeleteServers(subscribeLink.Remark); + Global.Settings.Subscription.Remove(subscribeLink); + + LoadSubscriptionLinks(); + } + + private void DeleteServersToolStripMenuItem_Click(object sender, EventArgs e) + { + if (MessageBoxX.Show(i18N.Translate("Confirm deletion?"), confirm: true) != DialogResult.OK) + return; + + DeleteServers(Global.Settings.Subscription[SelectedIndex].Remark); + } + + private void CopyLinkToolStripMenuItem_Click(object sender, EventArgs e) + { + Clipboard.SetText(Global.Settings.Subscription[SelectedIndex].Link); + } + + #endregion + + #region Helper + + private static void DeleteServers(string group) + { + Global.Settings.Server.RemoveAll(server => server.Group == group); + } + + private static void RenameServers(string oldGroup, string newGroup) + { + foreach (var server in Global.Settings.Server.Where(server => server.Group == oldGroup)) + server.Group = newGroup; + } + + private void LoadSubscriptionLinks() + { + SubscriptionLinkListView.Items.Clear(); + + foreach (var item in Global.Settings.Subscription) + SubscriptionLinkListView.Items.Add(new ListViewItem(new[] + { + "", + item.Remark, + item.Link, + !string.IsNullOrEmpty(item.UserAgent) ? item.UserAgent : WebUtil.DefaultUserAgent + }) + { + Checked = item.Enable + }); + + ResetEditingGroup(); + } + + private void ResetEditingGroup() + { + AddSubscriptionBox.Text = string.Empty; + RemarkTextBox.Text = string.Empty; + LinkTextBox.Text = string.Empty; + UserAgentTextBox.Text = WebUtil.DefaultUserAgent; + } + + private void SetEditingGroup(int index) + { + if (index == -1) + { + ResetEditingGroup(); + AddButton.Text = i18N.Translate("Add"); + return; + } + + var item = Global.Settings.Subscription[index]; + AddSubscriptionBox.Text = item.Remark; + RemarkTextBox.Text = item.Remark; + LinkTextBox.Text = item.Link; + UserAgentTextBox.Text = item.UserAgent; + + AddButton.Text = i18N.Translate("Modify"); + } + + #endregion } \ No newline at end of file diff --git a/Netch/Forms/SyncGlobalCheckBox.cs b/Netch/Forms/SyncGlobalCheckBox.cs index 6205117c..39d09a2e 100644 --- a/Netch/Forms/SyncGlobalCheckBox.cs +++ b/Netch/Forms/SyncGlobalCheckBox.cs @@ -1,92 +1,87 @@ -using System; -using System.Drawing; -using System.Windows.Forms; +namespace Netch.Forms; -namespace Netch.Forms +public class SyncGlobalCheckBox : CheckBox { - public class SyncGlobalCheckBox : CheckBox + public SyncGlobalCheckBox() { - public SyncGlobalCheckBox() + AutoCheck = false; + OnSyncGlobalChanged(); + } + + private bool _syncGlobal; + + private bool _globalValue; + + public bool SyncGlobal + { + get => _syncGlobal; + set { - AutoCheck = false; + if (value == _syncGlobal) + return; + + _syncGlobal = value; + OnSyncGlobalChanged(); } + } - private bool _syncGlobal; - - private bool _globalValue; - - public bool SyncGlobal + public bool GlobalValue + { + get => _globalValue; + set { - get => _syncGlobal; - set - { - if (value == _syncGlobal) - return; + if (value == _globalValue) + return; - _syncGlobal = value; + _globalValue = value; - OnSyncGlobalChanged(); - } + if (SyncGlobal) + Checked = value; + } + } + + protected override void OnClick(EventArgs e) + { + if (Checked == GlobalValue) + { + SyncGlobal = !SyncGlobal; + if (SyncGlobal) + return; } - public bool GlobalValue + Checked = !Checked; + base.OnClick(e); + } + + public bool? Value + { + get => _syncGlobal ? null : Checked; + set { - get => _globalValue; - set + if (value == null) { - if (value == _globalValue) - return; - - _globalValue = value; - - if (SyncGlobal) - Checked = value; - } - } - - protected override void OnClick(EventArgs e) - { - if (Checked == GlobalValue) - { - SyncGlobal = !SyncGlobal; - if (SyncGlobal) - return; - } - - Checked = !Checked; - base.OnClick(e); - } - - public bool? Value - { - get => _syncGlobal ? null : Checked; - set - { - if (value == null) - { - SyncGlobal = true; - } - else - { - SyncGlobal = false; - Checked = (bool)value; - } - } - } - - private void OnSyncGlobalChanged() - { - if (_syncGlobal) - { - Font = new Font(Font, FontStyle.Regular); - BackColor = SystemColors.Control; + SyncGlobal = true; } else { - Font = new Font(Font, FontStyle.Bold | FontStyle.Italic); - BackColor = Color.Yellow; + SyncGlobal = false; + Checked = (bool)value; } } } + + private void OnSyncGlobalChanged() + { + if (_syncGlobal) + { + Font = new Font(Font, FontStyle.Regular); + BackColor = SystemColors.Control; + } + else + { + Font = new Font(Font, FontStyle.Bold | FontStyle.Italic); + BackColor = Color.Yellow; + } + } } \ No newline at end of file diff --git a/Netch/Global.cs b/Netch/Global.cs index 230cbb4a..e765ef13 100644 --- a/Netch/Global.cs +++ b/Netch/Global.cs @@ -1,54 +1,50 @@ -using System; -using System.Collections.Generic; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; -using System.Windows.Forms; using Netch.Forms; using Netch.Models; using Netch.Models.Modes; using WindowsJobAPI; -namespace Netch +namespace Netch; + +public static class Global { - public static class Global + /// + /// 主窗体的静态实例 + /// + private static readonly Lazy LazyMainForm = new(() => new MainForm()); + + /// + /// 用于读取和写入的配置 + /// + public static Setting Settings = new(); + + public static readonly JobObject Job = new(); + + /// + /// 用于存储模式 + /// + public static readonly List Modes = new(); + + public static readonly string NetchDir; + public static readonly string NetchExecutable; + + static Global() { - /// - /// 主窗体的静态实例 - /// - private static readonly Lazy LazyMainForm = new(() => new MainForm()); - - /// - /// 用于读取和写入的配置 - /// - public static Setting Settings = new(); - - public static readonly JobObject Job = new(); - - /// - /// 用于存储模式 - /// - public static readonly List Modes = new(); - - public static readonly string NetchDir; - public static readonly string NetchExecutable; - - static Global() - { - NetchExecutable = Application.ExecutablePath; - NetchDir = Application.StartupPath; - } - - /// - /// 主窗体的静态实例 - /// - public static MainForm MainForm => LazyMainForm.Value; - - public static JsonSerializerOptions NewCustomJsonSerializerOptions() => new() - { - WriteIndented = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping - }; + NetchExecutable = Application.ExecutablePath; + NetchDir = Application.StartupPath; } + + /// + /// 主窗体的静态实例 + /// + public static MainForm MainForm => LazyMainForm.Value; + + public static JsonSerializerOptions NewCustomJsonSerializerOptions() => new() + { + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; } \ No newline at end of file diff --git a/Netch/Interfaces/IController.cs b/Netch/Interfaces/IController.cs index 80cb340e..3324c483 100644 --- a/Netch/Interfaces/IController.cs +++ b/Netch/Interfaces/IController.cs @@ -1,11 +1,8 @@ -using System.Threading.Tasks; +namespace Netch.Interfaces; -namespace Netch.Interfaces +public interface IController { - public interface IController - { - public string Name { get; } + public string Name { get; } - public Task StopAsync(); - } + public Task StopAsync(); } \ No newline at end of file diff --git a/Netch/Interfaces/IModeController.cs b/Netch/Interfaces/IModeController.cs index 9da824b8..02694bd2 100644 --- a/Netch/Interfaces/IModeController.cs +++ b/Netch/Interfaces/IModeController.cs @@ -1,13 +1,11 @@ -using System.Threading.Tasks; using Netch.Models.Modes; using Netch.Servers; -namespace Netch.Interfaces -{ - public interface IModeController : IController - { - public ModeFeature Features { get; } +namespace Netch.Interfaces; - public Task StartAsync(Socks5Server server, Mode mode); - } +public interface IModeController : IController +{ + public ModeFeature Features { get; } + + public Task StartAsync(Socks5Server server, Mode mode); } \ No newline at end of file diff --git a/Netch/Interfaces/IServerController.cs b/Netch/Interfaces/IServerController.cs index 2f06121f..0a985665 100644 --- a/Netch/Interfaces/IServerController.cs +++ b/Netch/Interfaces/IServerController.cs @@ -1,28 +1,26 @@ -using System.Threading.Tasks; -using Netch.Models; +using Netch.Models; using Netch.Servers; -namespace Netch.Interfaces +namespace Netch.Interfaces; + +public interface IServerController : IController { - public interface IServerController : IController + public ushort? Socks5LocalPort { get; set; } + + public string? LocalAddress { get; set; } + + public Task StartAsync(Server s); +} + +public static class ServerControllerExtension +{ + public static ushort Socks5LocalPort(this IServerController controller) { - public ushort? Socks5LocalPort { get; set; } - - public string? LocalAddress { get; set; } - - public Task StartAsync(Server s); + return controller.Socks5LocalPort ?? Global.Settings.Socks5LocalPort; } - public static class ServerControllerExtension + public static string LocalAddress(this IServerController controller) { - public static ushort Socks5LocalPort(this IServerController controller) - { - return controller.Socks5LocalPort ?? Global.Settings.Socks5LocalPort; - } - - public static string LocalAddress(this IServerController controller) - { - return controller.LocalAddress ?? Global.Settings.LocalAddress; - } + return controller.LocalAddress ?? Global.Settings.LocalAddress; } } \ No newline at end of file diff --git a/Netch/Interfaces/IServerUtil.cs b/Netch/Interfaces/IServerUtil.cs index 8cbf4377..5c5ba536 100644 --- a/Netch/Interfaces/IServerUtil.cs +++ b/Netch/Interfaces/IServerUtil.cs @@ -1,45 +1,42 @@ -using System; -using System.Collections.Generic; -using Netch.Models; +using Netch.Models; -namespace Netch.Interfaces +namespace Netch.Interfaces; + +public interface IServerUtil { - public interface IServerUtil - { - /// - /// Collection order basis - /// - ushort Priority { get; } + /// + /// Collection order basis + /// + ushort Priority { get; } - /// - /// Server.Type - /// - string TypeName { get; } + /// + /// Server.Type + /// + string TypeName { get; } - /// - /// Protocol Name - /// - string FullName { get; } + /// + /// Protocol Name + /// + string FullName { get; } - string ShortName { get; } + string ShortName { get; } - /// - /// Support URI - /// - string[] UriScheme { get; } + /// + /// Support URI + /// + string[] UriScheme { get; } - public Type ServerType { get; } + public Type ServerType { get; } - public void Edit(Server s); + public void Edit(Server s); - public void Create(); + public void Create(); - string GetShareLink(Server s); + string GetShareLink(Server s); - public IServerController GetController(); + public IServerController GetController(); - public IEnumerable ParseUri(string text); + public IEnumerable ParseUri(string text); - bool CheckServer(Server s); - } + bool CheckServer(Server s); } \ No newline at end of file diff --git a/Netch/Interops/AioDNS.cs b/Netch/Interops/AioDNS.cs index e476ccf3..dc67f388 100644 --- a/Netch/Interops/AioDNS.cs +++ b/Netch/Interops/AioDNS.cs @@ -1,46 +1,43 @@ using System.Runtime.InteropServices; using System.Text; -using System.Threading.Tasks; -using Serilog; -namespace Netch.Interops +namespace Netch.Interops; + +public static class AioDNS { - public static class AioDNS + private const string aiodns_bin = "aiodns.bin"; + + public static bool Dial(NameList name, string value) { - private const string aiodns_bin = "aiodns.bin"; + Log.Verbose($"[aiodns] Dial {name}: {value}"); + return aiodns_dial(name, Encoding.UTF8.GetBytes(value)); + } - public static bool Dial(NameList name, string value) - { - Log.Verbose($"[aiodns] Dial {name}: {value}"); - return aiodns_dial(name, Encoding.UTF8.GetBytes(value)); - } + public static async Task InitAsync() + { + return await Task.Run(Init).ConfigureAwait(false); + } - public static async Task InitAsync() - { - return await Task.Run(Init).ConfigureAwait(false); - } + public static async Task FreeAsync() + { + await Task.Run(Free).ConfigureAwait(false); + } - public static async Task FreeAsync() - { - await Task.Run(Free).ConfigureAwait(false); - } + [DllImport(aiodns_bin, CallingConvention = CallingConvention.Cdecl)] + private static extern bool aiodns_dial(NameList name, byte[] value); - [DllImport(aiodns_bin, CallingConvention = CallingConvention.Cdecl)] - private static extern bool aiodns_dial(NameList name, byte[] value); + [DllImport(aiodns_bin, EntryPoint = "aiodns_init", CallingConvention = CallingConvention.Cdecl)] + public static extern bool Init(); - [DllImport(aiodns_bin, EntryPoint = "aiodns_init", CallingConvention = CallingConvention.Cdecl)] - public static extern bool Init(); + [DllImport(aiodns_bin, EntryPoint = "aiodns_free", CallingConvention = CallingConvention.Cdecl)] + public static extern void Free(); - [DllImport(aiodns_bin, EntryPoint = "aiodns_free", CallingConvention = CallingConvention.Cdecl)] - public static extern void Free(); - - public enum NameList - { - TYPE_REST, - TYPE_LIST, - TYPE_LISN, - TYPE_CDNS, - TYPE_ODNS - } + public enum NameList + { + TYPE_REST, + TYPE_LIST, + TYPE_LISN, + TYPE_CDNS, + TYPE_ODNS } } \ No newline at end of file diff --git a/Netch/Interops/Redirector.cs b/Netch/Interops/Redirector.cs index a77d2165..6d0517f7 100644 --- a/Netch/Interops/Redirector.cs +++ b/Netch/Interops/Redirector.cs @@ -1,81 +1,78 @@ using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Serilog; -namespace Netch.Interops +namespace Netch.Interops; + +public static class Redirector { - public static class Redirector + public enum NameList { - public enum NameList - { - AIO_FILTERLOOPBACK, - AIO_FILTERINTRANET, // LAN - AIO_FILTERPARENT, - AIO_FILTERICMP, - AIO_FILTERTCP, - AIO_FILTERUDP, - AIO_FILTERDNS, + AIO_FILTERLOOPBACK, + AIO_FILTERINTRANET, // LAN + AIO_FILTERPARENT, + AIO_FILTERICMP, + AIO_FILTERTCP, + AIO_FILTERUDP, + AIO_FILTERDNS, - AIO_ICMPING, + AIO_ICMPING, - AIO_DNSONLY, - AIO_DNSPROX, - AIO_DNSHOST, - AIO_DNSPORT, + AIO_DNSONLY, + AIO_DNSPROX, + AIO_DNSHOST, + AIO_DNSPORT, - AIO_TGTHOST, - AIO_TGTPORT, - AIO_TGTUSER, - AIO_TGTPASS, + AIO_TGTHOST, + AIO_TGTPORT, + AIO_TGTUSER, + AIO_TGTPASS, - AIO_CLRNAME, - AIO_ADDNAME, - AIO_BYPNAME - } - - public static bool Dial(NameList name, bool value) - { - Log.Verbose($"[Redirector] Dial {name}: {value}"); - return aio_dial(name, value.ToString().ToLower()); - } - - public static bool Dial(NameList name, string value) - { - Log.Verbose($"[Redirector] Dial {name}: {value}"); - return aio_dial(name, value); - } - - public static async Task InitAsync() - { - return await Task.Run(aio_init).ConfigureAwait(false); - } - - public static async Task FreeAsync() - { - return await Task.Run(aio_free).ConfigureAwait(false); - } - - private const string Redirector_bin = "Redirector.bin"; - - [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] - public static extern bool aio_register([MarshalAs(UnmanagedType.LPWStr)] string value); - - [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] - public static extern bool aio_unregister([MarshalAs(UnmanagedType.LPWStr)] string value); - - [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] - private static extern bool aio_dial(NameList name, [MarshalAs(UnmanagedType.LPWStr)] string value); - - [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] - private static extern bool aio_init(); - - [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] - private static extern bool aio_free(); - - [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] - private static extern ulong aio_getUP(); - - [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] - private static extern ulong aio_getDL(); + AIO_CLRNAME, + AIO_ADDNAME, + AIO_BYPNAME } + + public static bool Dial(NameList name, bool value) + { + Log.Verbose($"[Redirector] Dial {name}: {value}"); + return aio_dial(name, value.ToString().ToLower()); + } + + public static bool Dial(NameList name, string value) + { + Log.Verbose($"[Redirector] Dial {name}: {value}"); + return aio_dial(name, value); + } + + public static async Task InitAsync() + { + return await Task.Run(aio_init).ConfigureAwait(false); + } + + public static async Task FreeAsync() + { + return await Task.Run(aio_free).ConfigureAwait(false); + } + + private const string Redirector_bin = "Redirector.bin"; + + [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] + public static extern bool aio_register([MarshalAs(UnmanagedType.LPWStr)] string value); + + [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] + public static extern bool aio_unregister([MarshalAs(UnmanagedType.LPWStr)] string value); + + [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] + private static extern bool aio_dial(NameList name, [MarshalAs(UnmanagedType.LPWStr)] string value); + + [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] + private static extern bool aio_init(); + + [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] + private static extern bool aio_free(); + + [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] + private static extern ulong aio_getUP(); + + [DllImport(Redirector_bin, CallingConvention = CallingConvention.Cdecl)] + private static extern ulong aio_getDL(); } \ No newline at end of file diff --git a/Netch/Interops/RouteHelper.cs b/Netch/Interops/RouteHelper.cs index ad33f68e..32de632b 100644 --- a/Netch/Interops/RouteHelper.cs +++ b/Netch/Interops/RouteHelper.cs @@ -1,107 +1,103 @@ -using System; using System.Net.Sockets; using System.Runtime.InteropServices; -using System.Threading; using Windows.Win32.Foundation; using Windows.Win32.Networking.WinSock; using Windows.Win32.NetworkManagement.IpHelper; -using Serilog; using static Windows.Win32.PInvoke; -namespace Netch.Interops +namespace Netch.Interops; + +public static unsafe class RouteHelper { - public static unsafe class RouteHelper + [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern ulong ConvertLuidToIndex(ulong id); + + [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool CreateIPv4(string address, string netmask, ulong index); + + public static bool CreateUnicastIP(AddressFamily inet, string address, byte cidr, ulong index) { - [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] - public static extern ulong ConvertLuidToIndex(ulong id); + MIB_UNICASTIPADDRESS_ROW addr; + InitializeUnicastIpAddressEntry(&addr); - [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] - public static extern bool CreateIPv4(string address, string netmask, ulong index); + addr.InterfaceIndex = (uint)index; + addr.OnLinkPrefixLength = cidr; - public static bool CreateUnicastIP(AddressFamily inet, string address, byte cidr, ulong index) + if (inet == AddressFamily.InterNetwork) { - MIB_UNICASTIPADDRESS_ROW addr; - InitializeUnicastIpAddressEntry(&addr); - - addr.InterfaceIndex = (uint)index; - addr.OnLinkPrefixLength = cidr; - - if (inet == AddressFamily.InterNetwork) - { - addr.Address.Ipv4.sin_family = (ushort)ADDRESS_FAMILY.AF_INET; - if (inet_pton((int)inet, address, &addr.Address.Ipv4.sin_addr) == 0) - return false; - } - else if (inet == AddressFamily.InterNetworkV6) - { - addr.Address.Ipv6.sin6_family = (ushort)ADDRESS_FAMILY.AF_INET6; - if (inet_pton((int)inet, address, &addr.Address.Ipv6.sin6_addr) == 0) - return false; - } - else - { + addr.Address.Ipv4.sin_family = (ushort)ADDRESS_FAMILY.AF_INET; + if (inet_pton((int)inet, address, &addr.Address.Ipv4.sin_addr) == 0) return false; - } + } + else if (inet == AddressFamily.InterNetworkV6) + { + addr.Address.Ipv6.sin6_family = (ushort)ADDRESS_FAMILY.AF_INET6; + if (inet_pton((int)inet, address, &addr.Address.Ipv6.sin6_addr) == 0) + return false; + } + else + { + return false; + } - // https://docs.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-createunicastipaddressentry#remarks + // https://docs.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-createunicastipaddressentry#remarks - HANDLE handle = default; - using var obj = new Semaphore(0, 1); + HANDLE handle = default; + using var obj = new Semaphore(0, 1); - void Callback(void* context, MIB_UNICASTIPADDRESS_ROW* row, MIB_NOTIFICATION_TYPE type) - { - if (type != MIB_NOTIFICATION_TYPE.MibInitialNotification) - { - // TODO pass error - NTSTATUS state; - if ((state = GetUnicastIpAddressEntry(row)) != 0) - { - Log.Error("CreateUnicastIpAddressEntry failed: {0}", state); - return; - } - - if (row -> DadState == NL_DAD_STATE.IpDadStatePreferred) - { - try - { - obj.Release(); - } - catch (Exception e) - { - // i don't trust win32 api - Log.Error(e, "semaphore disposed"); - } - } - } - } - - NotifyUnicastIpAddressChange((ushort)ADDRESS_FAMILY.AF_INET, Callback, null, new BOOLEAN(byte.MaxValue), ref handle); - - try + void Callback(void* context, MIB_UNICASTIPADDRESS_ROW* row, MIB_NOTIFICATION_TYPE type) + { + if (type != MIB_NOTIFICATION_TYPE.MibInitialNotification) { + // TODO pass error NTSTATUS state; - if ((state = CreateUnicastIpAddressEntry(&addr)) != 0) + if ((state = GetUnicastIpAddressEntry(row)) != 0) { Log.Error("CreateUnicastIpAddressEntry failed: {0}", state); - return false; + return; } - obj.WaitOne(); - return true; - } - finally - { - CancelMibChangeNotify2(handle); + if (row -> DadState == NL_DAD_STATE.IpDadStatePreferred) + { + try + { + obj.Release(); + } + catch (Exception e) + { + // i don't trust win32 api + Log.Error(e, "semaphore disposed"); + } + } } } - [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] - public static extern bool RefreshIPTable(AddressFamily inet, ulong index); + NotifyUnicastIpAddressChange((ushort)ADDRESS_FAMILY.AF_INET, Callback, null, new BOOLEAN(byte.MaxValue), ref handle); - [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] - public static extern bool CreateRoute(AddressFamily inet, string address, byte cidr, string gateway, ulong index, int metric); + try + { + NTSTATUS state; + if ((state = CreateUnicastIpAddressEntry(&addr)) != 0) + { + Log.Error("CreateUnicastIpAddressEntry failed: {0}", state); + return false; + } - [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] - public static extern bool DeleteRoute(AddressFamily inet, string address, byte cidr, string gateway, ulong index, int metric); + obj.WaitOne(); + return true; + } + finally + { + CancelMibChangeNotify2(handle); + } } + + [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool RefreshIPTable(AddressFamily inet, ulong index); + + [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool CreateRoute(AddressFamily inet, string address, byte cidr, string gateway, ulong index, int metric); + + [DllImport("RouteHelper.bin", CallingConvention = CallingConvention.Cdecl)] + public static extern bool DeleteRoute(AddressFamily inet, string address, byte cidr, string gateway, ulong index, int metric); } \ No newline at end of file diff --git a/Netch/JsonConverter/ModeConverterWithTypeDiscriminator.cs b/Netch/JsonConverter/ModeConverterWithTypeDiscriminator.cs index 905c937b..e5535fa5 100644 --- a/Netch/JsonConverter/ModeConverterWithTypeDiscriminator.cs +++ b/Netch/JsonConverter/ModeConverterWithTypeDiscriminator.cs @@ -1,44 +1,42 @@ -using System; -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; using Netch.Models.Modes; using Netch.Models.Modes.ProcessMode; using Netch.Models.Modes.ShareMode; using Netch.Models.Modes.TunMode; -namespace Netch.JsonConverter +namespace Netch.JsonConverter; + +public class ModeConverterWithTypeDiscriminator : JsonConverter { - public class ModeConverterWithTypeDiscriminator : JsonConverter + public override Mode? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - public override Mode? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + var jsonElement = JsonSerializer.Deserialize(ref reader); + + var modeTypePropertyName = JsonNamingPolicy.CamelCase.ConvertName(nameof(Mode.Type)); + if (!jsonElement.TryGetProperty(modeTypePropertyName, out var modeTypeToken)) + throw new JsonException(); + + var modeTypeEnum = modeTypeToken.ValueKind switch { - var jsonElement = JsonSerializer.Deserialize(ref reader); + JsonValueKind.Number => (ModeType)modeTypeToken.GetInt32(), + JsonValueKind.String => Enum.Parse(modeTypeToken.GetString()!), + _ => throw new JsonException() + }; - var modeTypePropertyName = JsonNamingPolicy.CamelCase.ConvertName(nameof(Mode.Type)); - if (!jsonElement.TryGetProperty(modeTypePropertyName, out var modeTypeToken)) - throw new JsonException(); - - var modeTypeEnum = modeTypeToken.ValueKind switch - { - JsonValueKind.Number => (ModeType)modeTypeToken.GetInt32(), - JsonValueKind.String => Enum.Parse(modeTypeToken.GetString()!), - _ => throw new JsonException() - }; - - var modeType = modeTypeEnum switch - { - ModeType.ProcessMode => typeof(Redirector), - ModeType.TunMode => typeof(TunMode), - ModeType.ShareMode => typeof(ShareMode), - _ => throw new ArgumentOutOfRangeException() - }; - - return (Mode?)jsonElement.Deserialize(modeType, options); - } - - public override void Write(Utf8JsonWriter writer, Mode value, JsonSerializerOptions options) + var modeType = modeTypeEnum switch { - JsonSerializer.Serialize(writer, value, options); - } + ModeType.ProcessMode => typeof(Redirector), + ModeType.TunMode => typeof(TunMode), + ModeType.ShareMode => typeof(ShareMode), + _ => throw new ArgumentOutOfRangeException() + }; + + return (Mode?)jsonElement.Deserialize(modeType, options); + } + + public override void Write(Utf8JsonWriter writer, Mode value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, options); } } \ No newline at end of file diff --git a/Netch/JsonConverter/ServerConverterWithTypeDiscriminator.cs b/Netch/JsonConverter/ServerConverterWithTypeDiscriminator.cs index 83155dc4..ac99cad1 100644 --- a/Netch/JsonConverter/ServerConverterWithTypeDiscriminator.cs +++ b/Netch/JsonConverter/ServerConverterWithTypeDiscriminator.cs @@ -1,23 +1,21 @@ -using System; -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; using Netch.Models; using Netch.Utils; -namespace Netch.JsonConverter -{ - public class ServerConverterWithTypeDiscriminator : JsonConverter - { - public override Server Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var jsonElement = JsonSerializer.Deserialize(ref reader); - var type = ServerHelper.GetTypeByTypeName(jsonElement.GetProperty("Type").GetString()!); - return (Server)jsonElement.Deserialize(type)!; - } +namespace Netch.JsonConverter; - public override void Write(Utf8JsonWriter writer, Server value, JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, value, options); - } +public class ServerConverterWithTypeDiscriminator : JsonConverter +{ + public override Server Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var jsonElement = JsonSerializer.Deserialize(ref reader); + var type = ServerHelper.GetTypeByTypeName(jsonElement.GetProperty("Type").GetString()!); + return (Server)jsonElement.Deserialize(type)!; + } + + public override void Write(Utf8JsonWriter writer, Server value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, options); } } \ No newline at end of file diff --git a/Netch/Models/Arguments.cs b/Netch/Models/Arguments.cs index e2e5a6cf..d9f7f51d 100644 --- a/Netch/Models/Arguments.cs +++ b/Netch/Models/Arguments.cs @@ -1,49 +1,45 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Netch.Utils; -namespace Netch.Models +namespace Netch.Models; + +public static class Arguments { - public static class Arguments + public static string Format(IEnumerable a) { - public static string Format(IEnumerable a) + var arguments = a.ToList(); + if (arguments.Count % 2 != 0) + throw new FormatException("missing last argument value"); + + var tokens = new List(); + + for (var i = 0; i < arguments.Count; i += 2) { - var arguments = a.ToList(); - if (arguments.Count % 2 != 0) - throw new FormatException("missing last argument value"); + var keyObj = arguments[i]; + var valueObj = arguments[i + 1]; - var tokens = new List(); + if (keyObj is not string key) + throw new FormatException($"argument key at array index {i} is not string"); - for (var i = 0; i < arguments.Count; i += 2) + switch (valueObj) { - var keyObj = arguments[i]; - var valueObj = arguments[i + 1]; - - if (keyObj is not string key) - throw new FormatException($"argument key at array index {i} is not string"); - - switch (valueObj) - { - case SpecialArgument.Flag: - tokens.Add(key); - break; - case null: - case string value when value.IsNullOrWhiteSpace(): - continue; - default: - tokens.Add(key); - tokens.Add(valueObj.ToString()!); - break; - } + case SpecialArgument.Flag: + tokens.Add(key); + break; + case null: + case string value when value.IsNullOrWhiteSpace(): + continue; + default: + tokens.Add(key); + tokens.Add(valueObj.ToString()!); + break; } - - return string.Join(' ', tokens); } - } - public enum SpecialArgument - { - Flag + return string.Join(' ', tokens); } +} + +public enum SpecialArgument +{ + Flag } \ No newline at end of file diff --git a/Netch/Models/GitHubRelease/Asset.cs b/Netch/Models/GitHubRelease/Asset.cs index 244a74e1..e0610c32 100644 --- a/Netch/Models/GitHubRelease/Asset.cs +++ b/Netch/Models/GitHubRelease/Asset.cs @@ -1,34 +1,31 @@ #nullable disable -using System; +namespace Netch.Models.GitHubRelease; -namespace Netch.Models.GitHubRelease +public class Asset { - public class Asset - { - public string url { get; set; } + public string url { get; set; } - public int id { get; set; } + public int id { get; set; } - public string node_id { get; set; } + public string node_id { get; set; } - public string name { get; set; } + public string name { get; set; } - public object label { get; set; } + public object label { get; set; } - public GitHubUser uploader { get; set; } + public GitHubUser uploader { get; set; } - public string content_type { get; set; } + public string content_type { get; set; } - public string state { get; set; } + public string state { get; set; } - public int size { get; set; } + public int size { get; set; } - public int download_count { get; set; } + public int download_count { get; set; } - public DateTime created_at { get; set; } + public DateTime created_at { get; set; } - public DateTime updated_at { get; set; } + public DateTime updated_at { get; set; } - public string browser_download_url { get; set; } - } + public string browser_download_url { get; set; } } \ No newline at end of file diff --git a/Netch/Models/GitHubRelease/GitHubRelease.cs b/Netch/Models/GitHubRelease/GitHubRelease.cs index c408f7a3..26ecc1e4 100644 --- a/Netch/Models/GitHubRelease/GitHubRelease.cs +++ b/Netch/Models/GitHubRelease/GitHubRelease.cs @@ -1,16 +1,15 @@ -namespace Netch.Models.GitHubRelease +namespace Netch.Models.GitHubRelease; + +public class GitHubRelease { - public class GitHubRelease + private readonly string _owner; + private readonly string _repo; + + public GitHubRelease(string owner, string repo) { - private readonly string _owner; - private readonly string _repo; - - public GitHubRelease(string owner, string repo) - { - _owner = owner; - _repo = repo; - } - - public string AllReleaseUrl => $@"https://api.github.com/repos/{_owner}/{_repo}/releases"; + _owner = owner; + _repo = repo; } + + public string AllReleaseUrl => $@"https://api.github.com/repos/{_owner}/{_repo}/releases"; } \ No newline at end of file diff --git a/Netch/Models/GitHubRelease/GitHubUser.cs b/Netch/Models/GitHubRelease/GitHubUser.cs index ad7d9bbb..3bc71b04 100644 --- a/Netch/Models/GitHubRelease/GitHubUser.cs +++ b/Netch/Models/GitHubRelease/GitHubUser.cs @@ -1,42 +1,41 @@ #nullable disable -namespace Netch.Models.GitHubRelease +namespace Netch.Models.GitHubRelease; + +public class GitHubUser { - public class GitHubUser - { - public string login { get; set; } + public string login { get; set; } - public int id { get; set; } + public int id { get; set; } - public string node_id { get; set; } + public string node_id { get; set; } - public string avatar_url { get; set; } + public string avatar_url { get; set; } - public string gravatar_id { get; set; } + public string gravatar_id { get; set; } - public string url { get; set; } + public string url { get; set; } - public string html_url { get; set; } + public string html_url { get; set; } - public string followers_url { get; set; } + public string followers_url { get; set; } - public string following_url { get; set; } + public string following_url { get; set; } - public string gists_url { get; set; } + public string gists_url { get; set; } - public string starred_url { get; set; } + public string starred_url { get; set; } - public string subscriptions_url { get; set; } + public string subscriptions_url { get; set; } - public string organizations_url { get; set; } + public string organizations_url { get; set; } - public string repos_url { get; set; } + public string repos_url { get; set; } - public string events_url { get; set; } + public string events_url { get; set; } - public string received_events_url { get; set; } + public string received_events_url { get; set; } - public string type { get; set; } + public string type { get; set; } - public bool site_admin { get; set; } - } + public bool site_admin { get; set; } } \ No newline at end of file diff --git a/Netch/Models/GitHubRelease/Release.cs b/Netch/Models/GitHubRelease/Release.cs index ad3ff78e..1de6c66d 100644 --- a/Netch/Models/GitHubRelease/Release.cs +++ b/Netch/Models/GitHubRelease/Release.cs @@ -1,44 +1,41 @@ #nullable disable -using System; +namespace Netch.Models.GitHubRelease; -namespace Netch.Models.GitHubRelease +public class Release { - public class Release - { - public string url { get; set; } + public string url { get; set; } - public string assets_url { get; set; } + public string assets_url { get; set; } - public string upload_url { get; set; } + public string upload_url { get; set; } - public string html_url { get; set; } + public string html_url { get; set; } - public int id { get; set; } + public int id { get; set; } - public string node_id { get; set; } + public string node_id { get; set; } - public string tag_name { get; set; } + public string tag_name { get; set; } - public string target_commitish { get; set; } + public string target_commitish { get; set; } - public string name { get; set; } + public string name { get; set; } - public bool draft { get; set; } + public bool draft { get; set; } - public GitHubUser author { get; set; } + public GitHubUser author { get; set; } - public bool prerelease { get; set; } + public bool prerelease { get; set; } - public DateTime created_at { get; set; } + public DateTime created_at { get; set; } - public DateTime published_at { get; set; } + public DateTime published_at { get; set; } - public Asset[] assets { get; set; } + public Asset[] assets { get; set; } - public string tarball_url { get; set; } + public string tarball_url { get; set; } - public string zipball_url { get; set; } + public string zipball_url { get; set; } - public string body { get; set; } - } + public string body { get; set; } } \ No newline at end of file diff --git a/Netch/Models/GitHubRelease/SuffixVersion.cs b/Netch/Models/GitHubRelease/SuffixVersion.cs index 954a9654..3a364700 100644 --- a/Netch/Models/GitHubRelease/SuffixVersion.cs +++ b/Netch/Models/GitHubRelease/SuffixVersion.cs @@ -1,110 +1,107 @@ -using System; -using System.Linq; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; -namespace Netch.Models.GitHubRelease +namespace Netch.Models.GitHubRelease; + +[Serializable] +public struct SuffixVersion : IComparable, IComparable { - [Serializable] - public struct SuffixVersion : IComparable, IComparable + public Version Version { get; } + + public string? Suffix { get; } + + public int SuffixNum { get; } + + private SuffixVersion(Version version) { - public Version Version { get; } + Version = version; + Suffix = null; + SuffixNum = 0; + } - public string? Suffix { get; } + private SuffixVersion(Version version, string suffix, int suffixNum) + { + Version = version; + Suffix = suffix; + SuffixNum = suffixNum; + } - public int SuffixNum { get; } + public static SuffixVersion Parse(string? value) + { + if (value == null) + throw new ArgumentNullException(nameof(value)); - private SuffixVersion(Version version) + var strings = value.Split('-'); + + var version = Version.Parse(strings[0]); + var suffix = strings.ElementAtOrDefault(1)?.Trim(); + switch (suffix) { - Version = version; - Suffix = null; - SuffixNum = 0; - } - - private SuffixVersion(Version version, string suffix, int suffixNum) - { - Version = version; - Suffix = suffix; - SuffixNum = suffixNum; - } - - public static SuffixVersion Parse(string? value) - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - - var strings = value.Split('-'); - - var version = Version.Parse(strings[0]); - var suffix = strings.ElementAtOrDefault(1)?.Trim(); - switch (suffix) + case null: + return new SuffixVersion(version); + case "": + throw new Exception("suffix WhiteSpace"); + default: { - case null: - return new SuffixVersion(version); - case "": - throw new Exception("suffix WhiteSpace"); - default: - { - var match = Regex.Match(suffix, @"(?\D+)(?\d+)"); - if (!match.Success) - throw new Exception(); + var match = Regex.Match(suffix, @"(?\D+)(?\d+)"); + if (!match.Success) + throw new Exception(); - return new SuffixVersion(version, match.Groups["suffix"].Value, int.Parse(match.Groups["num"].Value)); - } + return new SuffixVersion(version, match.Groups["suffix"].Value, int.Parse(match.Groups["num"].Value)); } } - - public static bool TryParse(string? input, out SuffixVersion result) - { - result = default; - try - { - result = Parse(input); - return true; - } - catch (Exception) - { - return false; - } - } - - public int CompareTo(object? obj) - { - if (obj is not SuffixVersion version) - throw new ArgumentOutOfRangeException(); - - return CompareTo(version); - } - - /// - /// - /// - /// - /// greater than 0 newer - /// - public int CompareTo(SuffixVersion other) - { - var versionComparison = Version.CompareTo(other.Version); - if (versionComparison != 0) - return versionComparison; - - var suffixExistComparison = (Suffix == null ? 1 : 0) - (other.Suffix == null ? 1 : 0); - if (suffixExistComparison != 0) - return suffixExistComparison; - - var suffixComparison = string.Compare(Suffix, other.Suffix, StringComparison.OrdinalIgnoreCase); - if (suffixComparison != 0) - return suffixComparison; - - return SuffixNum - other.SuffixNum; - } - - public override string ToString() - { - var s = Version.ToString(); - if (Suffix != null) - s += $"-{Suffix}{SuffixNum}"; - - return s; - } + } + + public static bool TryParse(string? input, out SuffixVersion result) + { + result = default; + try + { + result = Parse(input); + return true; + } + catch (Exception) + { + return false; + } + } + + public int CompareTo(object? obj) + { + if (obj is not SuffixVersion version) + throw new ArgumentOutOfRangeException(); + + return CompareTo(version); + } + + /// + /// + /// + /// + /// greater than 0 newer + /// + public int CompareTo(SuffixVersion other) + { + var versionComparison = Version.CompareTo(other.Version); + if (versionComparison != 0) + return versionComparison; + + var suffixExistComparison = (Suffix == null ? 1 : 0) - (other.Suffix == null ? 1 : 0); + if (suffixExistComparison != 0) + return suffixExistComparison; + + var suffixComparison = string.Compare(Suffix, other.Suffix, StringComparison.OrdinalIgnoreCase); + if (suffixComparison != 0) + return suffixComparison; + + return SuffixNum - other.SuffixNum; + } + + public override string ToString() + { + var s = Version.ToString(); + if (Suffix != null) + s += $"-{Suffix}{SuffixNum}"; + + return s; } } \ No newline at end of file diff --git a/Netch/Models/GitHubRelease/VersionUtil.cs b/Netch/Models/GitHubRelease/VersionUtil.cs index 4ed5b71e..34ee4823 100644 --- a/Netch/Models/GitHubRelease/VersionUtil.cs +++ b/Netch/Models/GitHubRelease/VersionUtil.cs @@ -1,35 +1,32 @@ -using System.Collections.Generic; +namespace Netch.Models.GitHubRelease; -namespace Netch.Models.GitHubRelease +public static class VersionUtil { - public static class VersionUtil + private static VersionComparer instance = new(); + + public static int CompareVersion(string x, string y) { - private static VersionComparer instance = new(); + return instance.Compare(x, y); + } - public static int CompareVersion(string x, string y) + public class VersionComparer : IComparer + { + /// + /// Greater than 0 newer + /// + /// + /// + /// + public int Compare(string? x, string? y) { - return instance.Compare(x, y); - } + var xResult = SuffixVersion.TryParse(x, out var version1) ? 1 : 0; + var yResult = SuffixVersion.TryParse(y, out var version2) ? 1 : 0; - public class VersionComparer : IComparer - { - /// - /// Greater than 0 newer - /// - /// - /// - /// - public int Compare(string? x, string? y) - { - var xResult = SuffixVersion.TryParse(x, out var version1) ? 1 : 0; - var yResult = SuffixVersion.TryParse(y, out var version2) ? 1 : 0; + var parseResult = xResult - yResult; + if (parseResult != 0) + return parseResult; - var parseResult = xResult - yResult; - if (parseResult != 0) - return parseResult; - - return version1.CompareTo(version2); - } + return version1.CompareTo(version2); } } } \ No newline at end of file diff --git a/Netch/Models/MessageException.cs b/Netch/Models/MessageException.cs index d2faf2a7..ac20a30b 100644 --- a/Netch/Models/MessageException.cs +++ b/Netch/Models/MessageException.cs @@ -1,15 +1,12 @@ -using System; +namespace Netch.Models; -namespace Netch.Models +public class MessageException : Exception { - public class MessageException : Exception + public MessageException() { - public MessageException() - { - } + } - public MessageException(string message) : base(message) - { - } + public MessageException(string message) : base(message) + { } } \ No newline at end of file diff --git a/Netch/Models/Modes/Mode.cs b/Netch/Models/Modes/Mode.cs index 87a66dc8..aeecbdca 100644 --- a/Netch/Models/Modes/Mode.cs +++ b/Netch/Models/Modes/Mode.cs @@ -1,29 +1,27 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Netch.Utils; -namespace Netch.Models.Modes +namespace Netch.Models.Modes; + +public abstract class Mode { - public abstract class Mode + [JsonPropertyOrder(int.MinValue)] + public abstract ModeType Type { get; } + + public Dictionary Remark { get; set; } = new(); + + [JsonIgnore] + // File FullName + // TODO maybe make it becomes mode dictionary key + public string FullName { get; set; } = string.Empty; + + public override string ToString() => $"[{(int)Type + 1}] {i18NRemark}"; + + [JsonIgnore] + public string i18NRemark { - [JsonPropertyOrder(int.MinValue)] - public abstract ModeType Type { get; } - - public Dictionary Remark { get; set; } = new(); - - [JsonIgnore] - // File FullName - // TODO maybe make it becomes mode dictionary key - public string FullName { get; set; } = string.Empty; - - public override string ToString() => $"[{(int)Type + 1}] {i18NRemark}"; - - [JsonIgnore] - public string i18NRemark - { - // TODO i18N.Culture to support fallback - get => Remark.GetValueOrDefault(i18N.LangCode) ?? Remark.GetValueOrDefault("en") ?? ""; - set => Remark[i18N.LangCode] = value; - } + // TODO i18N.Culture to support fallback + get => Remark.GetValueOrDefault(i18N.LangCode) ?? Remark.GetValueOrDefault("en") ?? ""; + set => Remark[i18N.LangCode] = value; } } \ No newline at end of file diff --git a/Netch/Models/Modes/ModeFeature.cs b/Netch/Models/Modes/ModeFeature.cs index 8e992582..2a00c5ce 100644 --- a/Netch/Models/Modes/ModeFeature.cs +++ b/Netch/Models/Modes/ModeFeature.cs @@ -1,13 +1,10 @@ -using System; +namespace Netch.Models.Modes; -namespace Netch.Models.Modes +[Flags] +public enum ModeFeature { - [Flags] - public enum ModeFeature - { - SupportSocks5 = 0, - SupportIPv4 = 0, - SupportSocks5Auth = 0b_0001, - SupportIPv6 = 0b_0100 - } + SupportSocks5 = 0, + SupportIPv4 = 0, + SupportSocks5Auth = 0b_0001, + SupportIPv6 = 0b_0100 } \ No newline at end of file diff --git a/Netch/Models/Modes/ModeType.cs b/Netch/Models/Modes/ModeType.cs index 14dfe8ad..ddba2c4c 100644 --- a/Netch/Models/Modes/ModeType.cs +++ b/Netch/Models/Modes/ModeType.cs @@ -1,20 +1,19 @@ -namespace Netch.Models.Modes +namespace Netch.Models.Modes; + +public enum ModeType { - public enum ModeType - { - /// - /// 进程代理 - /// - ProcessMode, + /// + /// 进程代理 + /// + ProcessMode, - /// - /// 网络共享 - /// - ShareMode, + /// + /// 网络共享 + /// + ShareMode, - /// - /// 网卡代理 - /// - TunMode - } -} + /// + /// 网卡代理 + /// + TunMode +} \ No newline at end of file diff --git a/Netch/Models/Modes/ProcessMode/ProcessMode.cs b/Netch/Models/Modes/ProcessMode/ProcessMode.cs index 77b5b73b..2ac0e145 100644 --- a/Netch/Models/Modes/ProcessMode/ProcessMode.cs +++ b/Netch/Models/Modes/ProcessMode/ProcessMode.cs @@ -1,39 +1,36 @@ -using System.Collections.Generic; +namespace Netch.Models.Modes.ProcessMode; -namespace Netch.Models.Modes.ProcessMode +public class Redirector : Mode { - public class Redirector : Mode - { - public override ModeType Type => ModeType.ProcessMode; + public override ModeType Type => ModeType.ProcessMode; - #region Base + #region Base - public bool? FilterICMP { get; set; } + public bool? FilterICMP { get; set; } - public bool? FilterTCP { get; set; } + public bool? FilterTCP { get; set; } - public bool? FilterUDP { get; set; } + public bool? FilterUDP { get; set; } - public bool? FilterDNS { get; set; } + public bool? FilterDNS { get; set; } - public bool? FilterParent { get; set; } + public bool? FilterParent { get; set; } - public int? ICMPDelay { get; set; } + public int? ICMPDelay { get; set; } - public bool? DNSProxy { get; set; } + public bool? DNSProxy { get; set; } - public bool? HandleOnlyDNS { get; set; } + public bool? HandleOnlyDNS { get; set; } - public string? DNSHost { get; set; } + public string? DNSHost { get; set; } - #endregion + #endregion - public bool FilterLoopback { get; set; } = false; + public bool FilterLoopback { get; set; } = false; - public bool FilterIntranet { get; set; } = true; + public bool FilterIntranet { get; set; } = true; - public List Bypass { get; set; } = new(); + public List Bypass { get; set; } = new(); - public List Handle { get; set; } = new(); - } + public List Handle { get; set; } = new(); } \ No newline at end of file diff --git a/Netch/Models/Modes/ShareMode/ShareMode.cs b/Netch/Models/Modes/ShareMode/ShareMode.cs index 2cc71249..954045a1 100644 --- a/Netch/Models/Modes/ShareMode/ShareMode.cs +++ b/Netch/Models/Modes/ShareMode/ShareMode.cs @@ -1,9 +1,8 @@ -namespace Netch.Models.Modes.ShareMode -{ - public class ShareMode : Mode - { - public override ModeType Type => ModeType.ShareMode; +namespace Netch.Models.Modes.ShareMode; - public string Argument = "--preset uu"; - } +public class ShareMode : Mode +{ + public override ModeType Type => ModeType.ShareMode; + + public string Argument = "--preset uu"; } \ No newline at end of file diff --git a/Netch/Models/Modes/TunMode/TunMode.cs b/Netch/Models/Modes/TunMode/TunMode.cs index a6ce85a0..6a6e7eee 100644 --- a/Netch/Models/Modes/TunMode/TunMode.cs +++ b/Netch/Models/Modes/TunMode/TunMode.cs @@ -1,13 +1,10 @@ -using System.Collections.Generic; +namespace Netch.Models.Modes.TunMode; -namespace Netch.Models.Modes.TunMode +public class TunMode : Mode { - public class TunMode : Mode - { - public override ModeType Type => ModeType.TunMode; + public override ModeType Type => ModeType.TunMode; - public List Bypass { get; set; } = new(); + public List Bypass { get; set; } = new(); - public List Handle { get; set; } = new(); - } + public List Handle { get; set; } = new(); } \ No newline at end of file diff --git a/Netch/Models/NatTypeTestResult.cs b/Netch/Models/NatTypeTestResult.cs index 3e262cce..195b2392 100644 --- a/Netch/Models/NatTypeTestResult.cs +++ b/Netch/Models/NatTypeTestResult.cs @@ -1,9 +1,8 @@ -namespace Netch.Models +namespace Netch.Models; + +public struct NatTypeTestResult { - public struct NatTypeTestResult - { - public string? Result; - public string? LocalEnd; - public string? PublicEnd; - } + public string? Result; + public string? LocalEnd; + public string? PublicEnd; } \ No newline at end of file diff --git a/Netch/Models/NetRoute.cs b/Netch/Models/NetRoute.cs index 5ff81d73..5589eb3f 100644 --- a/Netch/Models/NetRoute.cs +++ b/Netch/Models/NetRoute.cs @@ -1,49 +1,47 @@ -using System; -using System.Net; +using System.Net; using Windows.Win32; -namespace Netch.Models +namespace Netch.Models; + +public struct NetRoute { - public struct NetRoute + public static NetRoute TemplateBuilder(string gateway, int interfaceIndex, int metric = 0) { - public static NetRoute TemplateBuilder(string gateway, int interfaceIndex, int metric = 0) + return new() { - return new() - { - Gateway = gateway, - InterfaceIndex = interfaceIndex, - Metric = metric - }; - } + Gateway = gateway, + InterfaceIndex = interfaceIndex, + Metric = metric + }; + } - public static NetRoute GetBestRouteTemplate() - { - if (PInvoke.GetBestRoute(BitConverter.ToUInt32(IPAddress.Parse("114.114.114.114").GetAddressBytes(), 0), 0, out var route) != 0) - throw new MessageException("GetBestRoute 搜索失败"); + public static NetRoute GetBestRouteTemplate() + { + if (PInvoke.GetBestRoute(BitConverter.ToUInt32(IPAddress.Parse("114.114.114.114").GetAddressBytes(), 0), 0, out var route) != 0) + throw new MessageException("GetBestRoute 搜索失败"); - var gateway = new IPAddress(route.dwForwardNextHop); - return TemplateBuilder(gateway.ToString(), (int)route.dwForwardIfIndex); - } + var gateway = new IPAddress(route.dwForwardNextHop); + return TemplateBuilder(gateway.ToString(), (int)route.dwForwardIfIndex); + } - public int InterfaceIndex; + public int InterfaceIndex; - public string Gateway; + public string Gateway; - public string Network; + public string Network; - public byte Cidr; + public byte Cidr; - public int Metric; + public int Metric; - public NetRoute FillTemplate(string network, byte cidr, int? metric = null) - { - var o = (NetRoute)MemberwiseClone(); - o.Network = network; - o.Cidr = cidr; - if (metric != null) - o.Metric = (int)metric; + public NetRoute FillTemplate(string network, byte cidr, int? metric = null) + { + var o = (NetRoute)MemberwiseClone(); + o.Network = network; + o.Cidr = cidr; + if (metric != null) + o.Metric = (int)metric; - return o; - } + return o; } } \ No newline at end of file diff --git a/Netch/Models/NumberRange.cs b/Netch/Models/NumberRange.cs index 148f91fc..6058f23a 100644 --- a/Netch/Models/NumberRange.cs +++ b/Netch/Models/NumberRange.cs @@ -1,20 +1,19 @@ -namespace Netch.Models +namespace Netch.Models; + +public readonly struct NumberRange { - public readonly struct NumberRange + public int Start { get; } + + public int End { get; } + + public NumberRange(int start, int end) { - public int Start { get; } + Start = start; + End = end; + } - public int End { get; } - - public NumberRange(int start, int end) - { - Start = start; - End = end; - } - - public bool InRange(int num) - { - return Start <= num && num <= End; - } + public bool InRange(int num) + { + return Start <= num && num <= End; } } \ No newline at end of file diff --git a/Netch/Models/Profile.cs b/Netch/Models/Profile.cs index aef8be23..9f4153a5 100644 --- a/Netch/Models/Profile.cs +++ b/Netch/Models/Profile.cs @@ -1,31 +1,30 @@ using Netch.Models.Modes; -namespace Netch.Models +namespace Netch.Models; + +public class Profile { - public class Profile + public int Index { get; set; } + + public string ModeRemark { get; set; } + + public string ProfileName { get; set; } + + public string ServerRemark { get; set; } + + public Profile(Server server, Mode mode, string name, int index) { - public int Index { get; set; } + ServerRemark = server.Remark; + ModeRemark = mode.i18NRemark; + ProfileName = name; + Index = index; + } - public string ModeRemark { get; set; } - - public string ProfileName { get; set; } - - public string ServerRemark { get; set; } - - public Profile(Server server, Mode mode, string name, int index) - { - ServerRemark = server.Remark; - ModeRemark = mode.i18NRemark; - ProfileName = name; - Index = index; - } - - public Profile() - { - ServerRemark = string.Empty; - ModeRemark = string.Empty; - ProfileName = string.Empty; - Index = 0; - } + public Profile() + { + ServerRemark = string.Empty; + ModeRemark = string.Empty; + ProfileName = string.Empty; + Index = 0; } } \ No newline at end of file diff --git a/Netch/Models/Server.cs b/Netch/Models/Server.cs index 7454002a..59df4959 100644 --- a/Netch/Models/Server.cs +++ b/Netch/Models/Server.cs @@ -1,123 +1,120 @@ -using System; -using System.Net.Sockets; +using System.Net.Sockets; using System.Text.Json.Serialization; -using System.Threading.Tasks; using Netch.Utils; -namespace Netch.Models +namespace Netch.Models; + +public abstract class Server : ICloneable { - public abstract class Server : ICloneable + /// + /// 延迟 + /// + [JsonIgnore] + public int Delay { get; private set; } = -1; + + /// + /// 组 + /// + public string Group { get; set; } = Constants.DefaultGroup; + + /// + /// 地址 + /// + public string Hostname { get; set; } = string.Empty; + + /// + /// 端口 + /// + public ushort Port { get; set; } + + /// + /// 倍率 + /// + public double Rate { get; } = 1.0; + + /// + /// 备注 + /// + public string Remark { get; set; } = ""; + + /// + /// 代理类型 + /// + public abstract string Type { get; } + + public object Clone() { - /// - /// 延迟 - /// - [JsonIgnore] - public int Delay { get; private set; } = -1; + return MemberwiseClone(); + } - /// - /// 组 - /// - public string Group { get; set; } = Constants.DefaultGroup; + /// + /// 获取备注 + /// + /// 备注 + public override string ToString() + { + var remark = string.IsNullOrWhiteSpace(Remark) ? $"{Hostname}:{Port}" : Remark; - /// - /// 地址 - /// - public string Hostname { get; set; } = string.Empty; + var shortName = ServerHelper.GetUtilByTypeName(Type).ShortName; - /// - /// 端口 - /// - public ushort Port { get; set; } + return $"[{shortName}][{Group}] {remark}"; + } - /// - /// 倍率 - /// - public double Rate { get; } = 1.0; + public abstract string MaskedData(); - /// - /// 备注 - /// - public string Remark { get; set; } = ""; - - /// - /// 代理类型 - /// - public abstract string Type { get; } - - public object Clone() + /// + /// 测试延迟 + /// + /// 延迟 + public async Task PingAsync() + { + try { - return MemberwiseClone(); - } + var destination = await DnsUtils.LookupAsync(Hostname); + if (destination == null) + return Delay = -2; - /// - /// 获取备注 - /// - /// 备注 - public override string ToString() - { - var remark = string.IsNullOrWhiteSpace(Remark) ? $"{Hostname}:{Port}" : Remark; - - var shortName = ServerHelper.GetUtilByTypeName(Type).ShortName; - - return $"[{shortName}][{Group}] {remark}"; - } - - public abstract string MaskedData(); - - /// - /// 测试延迟 - /// - /// 延迟 - public async Task PingAsync() - { - try + var list = new Task[3]; + for (var i = 0; i < 3; i++) { - var destination = await DnsUtils.LookupAsync(Hostname); - if (destination == null) - return Delay = -2; - - var list = new Task[3]; - for (var i = 0; i < 3; i++) + async Task PingCoreAsync() { - async Task PingCoreAsync() + try { - try - { - return Global.Settings.ServerTCPing - ? await Utils.Utils.TCPingAsync(destination, Port) - : await Utils.Utils.ICMPingAsync(destination); - } - catch (Exception) - { - return -4; - } + return Global.Settings.ServerTCPing + ? await Utils.Utils.TCPingAsync(destination, Port) + : await Utils.Utils.ICMPingAsync(destination); + } + catch (Exception) + { + return -4; } - - list[i] = PingCoreAsync(); } - var resTask = await Task.WhenAny(list[0], list[1], list[2]); + list[i] = PingCoreAsync(); + } - return Delay = await resTask; - } - catch (Exception) - { - return Delay = -4; - } + var resTask = await Task.WhenAny(list[0], list[1], list[2]); + + return Delay = await resTask; + } + catch (Exception) + { + return Delay = -4; } } +} - public static class ServerExtension +public static class ServerExtension +{ + public static async Task AutoResolveHostnameAsync(this Server server, AddressFamily inet = AddressFamily.Unspecified) { - public static async Task AutoResolveHostnameAsync(this Server server, AddressFamily inet = AddressFamily.Unspecified) - { - // ! MainController cached - return (await DnsUtils.LookupAsync(server.Hostname, inet))!.ToString(); - } + // ! MainController cached + return (await DnsUtils.LookupAsync(server.Hostname, inet))!.ToString(); + } - public static bool IsInGroup(this Server server) - { - return server.Group is not Constants.DefaultGroup; - } + public static bool IsInGroup(this Server server) + { + return server.Group is not Constants.DefaultGroup; } } \ No newline at end of file diff --git a/Netch/Models/Settings/AioDNSConfig.cs b/Netch/Models/Settings/AioDNSConfig.cs index 50fd1af2..4e3ecb42 100644 --- a/Netch/Models/Settings/AioDNSConfig.cs +++ b/Netch/Models/Settings/AioDNSConfig.cs @@ -1,14 +1,13 @@ using System.Text.Json.Serialization; -namespace Netch.Models +namespace Netch.Models; + +public class AioDNSConfig { - public class AioDNSConfig - { - public string ChinaDNS { get; set; } = $"tcp://{Constants.DefaultCNPrimaryDNS}:53"; + public string ChinaDNS { get; set; } = $"tcp://{Constants.DefaultCNPrimaryDNS}:53"; - public string OtherDNS { get; set; } = $"tcp://{Constants.DefaultPrimaryDNS}:53"; + public string OtherDNS { get; set; } = $"tcp://{Constants.DefaultPrimaryDNS}:53"; - [JsonIgnore] - public ushort ListenPort { get; set; } = 53; - } + [JsonIgnore] + public ushort ListenPort { get; set; } = 53; } \ No newline at end of file diff --git a/Netch/Models/Settings/KcpConfig.cs b/Netch/Models/Settings/KcpConfig.cs index d7feb7a6..13bbb46d 100644 --- a/Netch/Models/Settings/KcpConfig.cs +++ b/Netch/Models/Settings/KcpConfig.cs @@ -1,19 +1,18 @@ -namespace Netch.Models +namespace Netch.Models; + +public class KcpConfig { - public class KcpConfig - { - public bool congestion { get; set; } = false; + public bool congestion { get; set; } = false; - public int downlinkCapacity { get; set; } = 100; + public int downlinkCapacity { get; set; } = 100; - public int mtu { get; set; } = 1350; + public int mtu { get; set; } = 1350; - public int readBufferSize { get; set; } = 2; + public int readBufferSize { get; set; } = 2; - public int tti { get; set; } = 50; + public int tti { get; set; } = 50; - public int uplinkCapacity { get; set; } = 12; + public int uplinkCapacity { get; set; } = 12; - public int writeBufferSize { get; set; } = 2; - } + public int writeBufferSize { get; set; } = 2; } \ No newline at end of file diff --git a/Netch/Models/Settings/RedirectorConfig.cs b/Netch/Models/Settings/RedirectorConfig.cs index 36afd91a..ba620e77 100644 --- a/Netch/Models/Settings/RedirectorConfig.cs +++ b/Netch/Models/Settings/RedirectorConfig.cs @@ -1,23 +1,22 @@ -namespace Netch.Models +namespace Netch.Models; + +public class RedirectorConfig { - public class RedirectorConfig - { - public bool FilterTCP { get; set; } = true; + public bool FilterTCP { get; set; } = true; - public bool FilterUDP { get; set; } = true; + public bool FilterUDP { get; set; } = true; - public bool FilterDNS { get; set; } = true; + public bool FilterDNS { get; set; } = true; - public bool FilterParent { get; set; } = false; + public bool FilterParent { get; set; } = false; - public bool HandleOnlyDNS { get; set; } = true; + public bool HandleOnlyDNS { get; set; } = true; - public bool DNSProxy { get; set; } = true; + public bool DNSProxy { get; set; } = true; - public string DNSHost { get; set; } = $"{Constants.DefaultPrimaryDNS}:53"; + public string DNSHost { get; set; } = $"{Constants.DefaultPrimaryDNS}:53"; - public int ICMPDelay { get; set; } = 10; + public int ICMPDelay { get; set; } = 10; - public bool FilterICMP { get; set; } = false; - } + public bool FilterICMP { get; set; } = false; } \ No newline at end of file diff --git a/Netch/Models/Settings/Setting.cs b/Netch/Models/Settings/Setting.cs index 8aebe6c4..8eadbf16 100644 --- a/Netch/Models/Settings/Setting.cs +++ b/Netch/Models/Settings/Setting.cs @@ -1,176 +1,172 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; +using System.Text.Json; -namespace Netch.Models +namespace Netch.Models; + +/// +/// 用于读取和写入的配置的类 +/// +public class Setting { + public RedirectorConfig Redirector { get; set; } = new(); + /// - /// 用于读取和写入的配置的类 + /// 服务器列表 /// - public class Setting + public List Server { get; set; } = new(); + + public AioDNSConfig AioDNS { get; set; } = new(); + + /// + /// 是否检查 Beta 更新 + /// + public bool CheckBetaUpdate { get; set; } = false; + + /// + /// 是否打开软件时检查更新 + /// + public bool CheckUpdateWhenOpened { get; set; } = true; + + /// + /// 测试所有服务器心跳/秒 + /// + public int DetectionTick { get; set; } = 10; + + /// + /// 是否关闭窗口时退出 + /// + public bool ExitWhenClosed { get; set; } = false; + + /// + /// HTTP 本地端口 + /// + public ushort HTTPLocalPort { get; set; } = 2802; + + /// + /// 语言设置 + /// + public string Language { get; set; } = "System"; + + /// + /// HTTP 和 Socks5 本地代理地址 + /// + public string LocalAddress { get; set; } = "127.0.0.1"; + + /// + /// 是否启动后自动最小化 + /// + public bool MinimizeWhenStarted { get; set; } = false; + + /// + /// 模式选择位置 + /// + public int ModeComboBoxSelectedIndex { get; set; } = -1; + + /// + /// 快捷配置数量 + /// + public int ProfileCount { get; set; } = 4; + + /// + /// 已保存的快捷配置 + /// + public List Profiles { get; set; } = new(); + + /// + /// 配置最大列数 + /// + public byte ProfileTableColumnCount { get; set; } = 5; + + /// + /// 网页请求超时 毫秒 + /// + public int RequestTimeout { get; set; } = 10000; + + /// + /// 是否开机启动软件 + /// + public bool RunAtStartup { get; set; } = false; + + /// + /// 服务器选择位置 + /// + public int ServerComboBoxSelectedIndex { get; set; } = -1; + + /// + /// 服务器测试方式 false.ICMPing true.TCPing + /// + public bool ServerTCPing { get; set; } = true; + + /// + /// Socks5 本地端口 + /// + public ushort Socks5LocalPort { get; set; } = 2801; + + /// + /// 启动后延迟测试间隔/秒 + /// + public int StartedPingInterval { get; set; } = -1; + + /// + /// 是否打开软件时启动加速 + /// + public bool StartWhenOpened { get; set; } = false; + + /// + /// 是否退出时停止 + /// + public bool StopWhenExited { get; set; } = false; + + /// + /// STUN测试服务器 + /// + public string STUN_Server { get; set; } = "stun.syncthing.net"; + + /// + /// STUN测试服务器 + /// + public int STUN_Server_Port { get; set; } = 3478; + + /// + /// 订阅链接列表 + /// + public List Subscription { get; set; } = new(); + + /// + /// TUNTAP 适配器配置 + /// + public TUNConfig TUNTAP { get; set; } = new(); + + /// + /// 是否打开软件时更新订阅 + /// + public bool UpdateServersWhenOpened { get; set; } = false; + + public V2rayConfig V2RayConfig { get; set; } = new(); + + public bool NoSupportDialog { get; set; } = false; + + #region Migration + + [Obsolete] + public JsonElement SubscribeLink { - public RedirectorConfig Redirector { get; set; } = new(); - - /// - /// 服务器列表 - /// - public List Server { get; set; } = new(); - - public AioDNSConfig AioDNS { get; set; } = new(); - - /// - /// 是否检查 Beta 更新 - /// - public bool CheckBetaUpdate { get; set; } = false; - - /// - /// 是否打开软件时检查更新 - /// - public bool CheckUpdateWhenOpened { get; set; } = true; - - /// - /// 测试所有服务器心跳/秒 - /// - public int DetectionTick { get; set; } = 10; - - /// - /// 是否关闭窗口时退出 - /// - public bool ExitWhenClosed { get; set; } = false; - - /// - /// HTTP 本地端口 - /// - public ushort HTTPLocalPort { get; set; } = 2802; - - /// - /// 语言设置 - /// - public string Language { get; set; } = "System"; - - /// - /// HTTP 和 Socks5 本地代理地址 - /// - public string LocalAddress { get; set; } = "127.0.0.1"; - - /// - /// 是否启动后自动最小化 - /// - public bool MinimizeWhenStarted { get; set; } = false; - - /// - /// 模式选择位置 - /// - public int ModeComboBoxSelectedIndex { get; set; } = -1; - - /// - /// 快捷配置数量 - /// - public int ProfileCount { get; set; } = 4; - - /// - /// 已保存的快捷配置 - /// - public List Profiles { get; set; } = new(); - - /// - /// 配置最大列数 - /// - public byte ProfileTableColumnCount { get; set; } = 5; - - /// - /// 网页请求超时 毫秒 - /// - public int RequestTimeout { get; set; } = 10000; - - /// - /// 是否开机启动软件 - /// - public bool RunAtStartup { get; set; } = false; - - /// - /// 服务器选择位置 - /// - public int ServerComboBoxSelectedIndex { get; set; } = -1; - - /// - /// 服务器测试方式 false.ICMPing true.TCPing - /// - public bool ServerTCPing { get; set; } = true; - - /// - /// Socks5 本地端口 - /// - public ushort Socks5LocalPort { get; set; } = 2801; - - /// - /// 启动后延迟测试间隔/秒 - /// - public int StartedPingInterval { get; set; } = -1; - - /// - /// 是否打开软件时启动加速 - /// - public bool StartWhenOpened { get; set; } = false; - - /// - /// 是否退出时停止 - /// - public bool StopWhenExited { get; set; } = false; - - /// - /// STUN测试服务器 - /// - public string STUN_Server { get; set; } = "stun.syncthing.net"; - - /// - /// STUN测试服务器 - /// - public int STUN_Server_Port { get; set; } = 3478; - - /// - /// 订阅链接列表 - /// - public List Subscription { get; set; } = new(); - - /// - /// TUNTAP 适配器配置 - /// - public TUNConfig TUNTAP { get; set; } = new(); - - /// - /// 是否打开软件时更新订阅 - /// - public bool UpdateServersWhenOpened { get; set; } = false; - - public V2rayConfig V2RayConfig { get; set; } = new(); - - public bool NoSupportDialog { get; set; } = false; - - #region Migration - - [Obsolete] - public JsonElement SubscribeLink + set { - set - { - if (Subscription == null! || !Subscription.Any()) - Subscription = value.Deserialize>()!; - } - } - - #endregion - - public Setting ShallowCopy() - { - return (Setting)MemberwiseClone(); - } - - public void Set(Setting value) - { - foreach (var p in typeof(Setting).GetProperties()) - p.SetValue(this, p.GetValue(value)); + if (Subscription == null! || !Subscription.Any()) + Subscription = value.Deserialize>()!; } } + + #endregion + + public Setting ShallowCopy() + { + return (Setting)MemberwiseClone(); + } + + public void Set(Setting value) + { + foreach (var p in typeof(Setting).GetProperties()) + p.SetValue(this, p.GetValue(value)); + } } \ No newline at end of file diff --git a/Netch/Models/Settings/TUNConfig.cs b/Netch/Models/Settings/TUNConfig.cs index 0c3b1926..f57837f9 100644 --- a/Netch/Models/Settings/TUNConfig.cs +++ b/Netch/Models/Settings/TUNConfig.cs @@ -1,45 +1,42 @@ -using System.Collections.Generic; +namespace Netch.Models; -namespace Netch.Models +/// +/// TUN/TAP 适配器配置类 +/// +public class TUNConfig { /// - /// TUN/TAP 适配器配置类 + /// 地址 /// - public class TUNConfig - { - /// - /// 地址 - /// - public string Address { get; set; } = "10.0.236.10"; + public string Address { get; set; } = "10.0.236.10"; - /// - /// DNS - /// - public string DNS { get; set; } = Constants.DefaultPrimaryDNS; + /// + /// DNS + /// + public string DNS { get; set; } = Constants.DefaultPrimaryDNS; - /// - /// 网关 - /// - public string Gateway { get; set; } = "10.0.236.1"; + /// + /// 网关 + /// + public string Gateway { get; set; } = "10.0.236.1"; - /// - /// 掩码 - /// - public string Netmask { get; set; } = "255.255.255.0"; + /// + /// 掩码 + /// + public string Netmask { get; set; } = "255.255.255.0"; - /// - /// 模式 2 下是否代理 DNS - /// - public bool ProxyDNS { get; set; } = false; + /// + /// 模式 2 下是否代理 DNS + /// + public bool ProxyDNS { get; set; } = false; - /// - /// 使用自定义 DNS 设置 - /// - public bool UseCustomDNS { get; set; } = false; + /// + /// 使用自定义 DNS 设置 + /// + public bool UseCustomDNS { get; set; } = false; - /// - /// Global bypass IPs - /// - public List BypassIPs { get; set; } = new(); - } + /// + /// Global bypass IPs + /// + public List BypassIPs { get; set; } = new(); } \ No newline at end of file diff --git a/Netch/Models/Settings/V2rayConfig.cs b/Netch/Models/Settings/V2rayConfig.cs index f3bbdfe0..6ae13dba 100644 --- a/Netch/Models/Settings/V2rayConfig.cs +++ b/Netch/Models/Settings/V2rayConfig.cs @@ -1,15 +1,14 @@ -namespace Netch.Models +namespace Netch.Models; + +public class V2rayConfig { - public class V2rayConfig - { - public bool AllowInsecure { get; set; } = false; + public bool AllowInsecure { get; set; } = false; - public KcpConfig KcpConfig { get; set; } = new(); + public KcpConfig KcpConfig { get; set; } = new(); - public bool UseMux { get; set; } = false; + public bool UseMux { get; set; } = false; - public bool V2rayNShareLink { get; set; } = true; + public bool V2rayNShareLink { get; set; } = true; - public bool XrayCone { get; set; } = true; - } + public bool XrayCone { get; set; } = true; } \ No newline at end of file diff --git a/Netch/Models/StatusText.cs b/Netch/Models/StatusText.cs index f0d7395a..fe7a32d7 100644 --- a/Netch/Models/StatusText.cs +++ b/Netch/Models/StatusText.cs @@ -1,52 +1,49 @@ using Netch.Utils; -using System.Collections.Generic; -using System.Linq; -namespace Netch.Models +namespace Netch.Models; + +public static class StatusPortInfoText { - public static class StatusPortInfoText + private static ushort? _socks5Port; + private static ushort? _httpPort; + private static bool _shareLan; + + public static ushort HttpPort { - private static ushort? _socks5Port; - private static ushort? _httpPort; - private static bool _shareLan; + set => _httpPort = value; + } - public static ushort HttpPort + public static ushort Socks5Port + { + set => _socks5Port = value; + } + + public static string Value + { + get { - set => _httpPort = value; - } + var strings = new List(); - public static ushort Socks5Port - { - set => _socks5Port = value; - } + if (_socks5Port != null) + strings.Add($"Socks5 {i18N.Translate("Local Port", ": ")}{_socks5Port}"); - public static string Value - { - get - { - var strings = new List(); + if (_httpPort != null) + strings.Add($"HTTP {i18N.Translate("Local Port", ": ")}{_httpPort}"); - if (_socks5Port != null) - strings.Add($"Socks5 {i18N.Translate("Local Port", ": ")}{_socks5Port}"); + if (!strings.Any()) + return string.Empty; - if (_httpPort != null) - strings.Add($"HTTP {i18N.Translate("Local Port", ": ")}{_httpPort}"); - - if (!strings.Any()) - return string.Empty; - - return $" ({(_shareLan ? i18N.Translate("Allow other Devices to connect") + " " : "")}{string.Join(" | ", strings)})"; - } - } - - public static void UpdateShareLan() - { - _shareLan = Global.Settings.LocalAddress != "127.0.0.1"; - } - - public static void Reset() - { - _httpPort = _socks5Port = null; + return $" ({(_shareLan ? i18N.Translate("Allow other Devices to connect") + " " : "")}{string.Join(" | ", strings)})"; } } + + public static void UpdateShareLan() + { + _shareLan = Global.Settings.LocalAddress != "127.0.0.1"; + } + + public static void Reset() + { + _httpPort = _socks5Port = null; + } } \ No newline at end of file diff --git a/Netch/Models/Subscription.cs b/Netch/Models/Subscription.cs index d4711123..bb83a199 100644 --- a/Netch/Models/Subscription.cs +++ b/Netch/Models/Subscription.cs @@ -1,25 +1,24 @@ -namespace Netch.Models +namespace Netch.Models; + +public class Subscription { - public class Subscription - { - /// - /// 启用状态 - /// - public bool Enable { get; set; } = true; + /// + /// 启用状态 + /// + public bool Enable { get; set; } = true; - /// - /// 链接 - /// - public string Link { get; set; } = string.Empty; + /// + /// 链接 + /// + public string Link { get; set; } = string.Empty; - /// - /// 备注 - /// - public string Remark { get; set; } = string.Empty; + /// + /// 备注 + /// + public string Remark { get; set; } = string.Empty; - /// - /// User Agent - /// - public string UserAgent { get; set; } = string.Empty; - } + /// + /// User Agent + /// + public string UserAgent { get; set; } = string.Empty; } \ No newline at end of file diff --git a/Netch/Models/TagItem.cs b/Netch/Models/TagItem.cs index 96afbcbe..bd26a3f0 100644 --- a/Netch/Models/TagItem.cs +++ b/Netch/Models/TagItem.cs @@ -1,19 +1,18 @@ using Netch.Utils; -namespace Netch.Models +namespace Netch.Models; + +internal class TagItem { - internal class TagItem + private readonly string _text; + + public TagItem(T value, string text) { - private readonly string _text; - - public TagItem(T value, string text) - { - _text = text; - Value = value; - } - - public string Text => i18N.Translate(_text); - - public T Value { get; } + _text = text; + Value = value; } + + public string Text => i18N.Translate(_text); + + public T Value { get; } } \ No newline at end of file diff --git a/Netch/NativeMethods.cs b/Netch/NativeMethods.cs index 194ba651..e66c253b 100644 --- a/Netch/NativeMethods.cs +++ b/Netch/NativeMethods.cs @@ -1,10 +1,9 @@ using System.Runtime.InteropServices; -namespace Netch +namespace Netch; + +public static class NativeMethods { - public static class NativeMethods - { - [DllImport("dnsapi", EntryPoint = "DnsFlushResolverCache")] - public static extern uint RefreshDNSCache(); - } -} + [DllImport("dnsapi", EntryPoint = "DnsFlushResolverCache")] + public static extern uint RefreshDNSCache(); +} \ No newline at end of file diff --git a/Netch/Netch.csproj b/Netch/Netch.csproj index f443085b..7d23448b 100644 --- a/Netch/Netch.csproj +++ b/Netch/Netch.csproj @@ -1,83 +1,91 @@  - + - - WinExe - Debug;Release - true - false - App.manifest - false - false - Resources\Netch.ico - false - VSTHRD100 - false - Default - true - + + WinExe + Debug;Release + true + enable + false + App.manifest + false + false + Resources\Netch.ico + false + + VSTHRD100 + true + Default + true + true + - - bin\x64\Debug\ - DEBUG;TRACE - + + bin\x64\Debug\ + DEBUG;TRACE + - - bin\x64\Release\ - none - false - + + bin\x64\Release\ + none + false + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + True + True + Resources.resx + + + UserControl + + + + + + + + - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - + <_FilesToBundle Remove="$(PkgMicrosoft_Diagnostics_Tracing_TraceEvent)\build\native\x86\**" /> + - - - ResXFileCodeGenerator - Resources.Designer.cs - - - True - True - Resources.resx - - - UserControl - - - - - - - - - - <_FilesToBundle Remove="$(PkgMicrosoft_Diagnostics_Tracing_TraceEvent)\build\native\x86\**" /> - - diff --git a/Netch/Program.cs b/Netch/Program.cs index 244c5424..df8cbc24 100644 --- a/Netch/Program.cs +++ b/Netch/Program.cs @@ -1,12 +1,6 @@ -using System; -using System.Globalization; -using System.IO; -using System.Linq; +using System.Globalization; using System.Reflection; using System.Runtime.Versioning; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; using Windows.Win32; using Windows.Win32.Foundation; using Microsoft.VisualStudio.Threading; @@ -15,206 +9,204 @@ using Netch.Enums; using Netch.Forms; using Netch.Services; using Netch.Utils; -using Serilog; using Serilog.Events; using SingleInstance; #if RELEASE using Windows.Win32.UI.WindowsAndMessaging; #endif -namespace Netch -{ - public static class Program - { - public static readonly ISingleInstanceService SingleInstance = new SingleInstanceService($"Global\\{nameof(Netch)}"); +namespace Netch; - internal static HWND ConsoleHwnd { get; private set; } +public static class Program +{ + public static readonly ISingleInstanceService SingleInstance = new SingleInstanceService($"Global\\{nameof(Netch)}"); + + internal static HWND ConsoleHwnd { get; private set; } #pragma warning disable VSTHRD002 - // VSTHRD002: Avoid problematic synchronous waits - // Main never re-called, so we can ignore this + // VSTHRD002: Avoid problematic synchronous waits + // Main never re-called, so we can ignore this - [STAThread] - public static void Main(string[] args) + [STAThread] + public static void Main(string[] args) + { + // handle arguments + if (args.Contains(Constants.Parameter.ForceUpdate)) + Flags.AlwaysShowNewVersionFound = true; + + // set working directory + Directory.SetCurrentDirectory(Global.NetchDir); + + // append .\bin to PATH + var binPath = Path.Combine(Global.NetchDir, "bin"); + Environment.SetEnvironmentVariable("PATH", $"{Environment.GetEnvironmentVariable("PATH")};{binPath}"); + + // check if .\bin directory exists + if (!Directory.Exists("bin") || !Directory.EnumerateFileSystemEntries("bin").Any()) { - // handle arguments - if (args.Contains(Constants.Parameter.ForceUpdate)) - Flags.AlwaysShowNewVersionFound = true; - - // set working directory - Directory.SetCurrentDirectory(Global.NetchDir); - - // append .\bin to PATH - var binPath = Path.Combine(Global.NetchDir, "bin"); - Environment.SetEnvironmentVariable("PATH", $"{Environment.GetEnvironmentVariable("PATH")};{binPath}"); - - // check if .\bin directory exists - if (!Directory.Exists("bin") || !Directory.EnumerateFileSystemEntries("bin").Any()) - { - i18N.Load("System"); - MessageBoxX.Show(i18N.Translate("Please extract all files then run the program!")); - Environment.Exit(2); - } - - // clean up old files - Updater.CleanOld(Global.NetchDir); - - // pre-create directories - var directories = new[] { "mode\\Custom", "data", "i18n", "logging" }; - foreach (var item in directories) - if (!Directory.Exists(item)) - Directory.CreateDirectory(item); - - // load configuration - Configuration.LoadAsync().Wait(); - - // check if the program is already running - if (!SingleInstance.TryStartSingleInstance()) - { - SingleInstance.SendMessageToFirstInstanceAsync(Constants.Parameter.Show).GetAwaiter().GetResult(); - Environment.Exit(0); - return; - } - - SingleInstance.Received.Subscribe(SingleInstance_ArgumentsReceived); - - // clean up old logs - if (Directory.Exists("logging")) - { - var directory = new DirectoryInfo("logging"); - - foreach (var file in directory.GetFiles()) - file.Delete(); - - foreach (var dir in directory.GetDirectories()) - dir.Delete(true); - } - - InitConsole(); - - CreateLogger(); - - // load i18n - i18N.Load(Global.Settings.Language); - - // log environment information - Task.Run(LogEnvironment).Forget(); - CheckClr(); - CheckOS(); - - // handle exceptions - Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); - Application.ThreadException += Application_OnException; - Application.ApplicationExit += Application_OnExit; - - Application.SetHighDpiMode(HighDpiMode.DpiUnawareGdiScaled); - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(Global.MainForm); + i18N.Load("System"); + MessageBoxX.Show(i18N.Translate("Please extract all files then run the program!")); + Environment.Exit(2); } + // clean up old files + Updater.CleanOld(Global.NetchDir); + + // pre-create directories + var directories = new[] { "mode\\Custom", "data", "i18n", "logging" }; + foreach (var item in directories) + if (!Directory.Exists(item)) + Directory.CreateDirectory(item); + + // load configuration + Configuration.LoadAsync().Wait(); + + // check if the program is already running + if (!SingleInstance.TryStartSingleInstance()) + { + SingleInstance.SendMessageToFirstInstanceAsync(Constants.Parameter.Show).GetAwaiter().GetResult(); + Environment.Exit(0); + return; + } + + SingleInstance.Received.Subscribe(SingleInstance_ArgumentsReceived); + + // clean up old logs + if (Directory.Exists("logging")) + { + var directory = new DirectoryInfo("logging"); + + foreach (var file in directory.GetFiles()) + file.Delete(); + + foreach (var dir in directory.GetDirectories()) + dir.Delete(true); + } + + InitConsole(); + + CreateLogger(); + + // load i18n + i18N.Load(Global.Settings.Language); + + // log environment information + Task.Run(LogEnvironment).Forget(); + CheckClr(); + CheckOS(); + + // handle exceptions + Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); + Application.ThreadException += Application_OnException; + Application.ApplicationExit += Application_OnExit; + + Application.SetHighDpiMode(HighDpiMode.DpiUnawareGdiScaled); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(Global.MainForm); + } + #pragma warning restore VSTHRD002 - private static void LogEnvironment() - { - Log.Information("Netch Version: {Version}", $"{UpdateChecker.Owner}/{UpdateChecker.Repo}@{UpdateChecker.Version}"); - Log.Information("OS: {OSVersion}", Environment.OSVersion); - Log.Information("SHA256: {Hash}", $"{Utils.Utils.SHA256CheckSum(Global.NetchExecutable)}"); - Log.Information("System Language: {Language}", CultureInfo.CurrentCulture.Name); + private static void LogEnvironment() + { + Log.Information("Netch Version: {Version}", $"{UpdateChecker.Owner}/{UpdateChecker.Repo}@{UpdateChecker.Version}"); + Log.Information("OS: {OSVersion}", Environment.OSVersion); + Log.Information("SHA256: {Hash}", $"{Utils.Utils.SHA256CheckSum(Global.NetchExecutable)}"); + Log.Information("System Language: {Language}", CultureInfo.CurrentCulture.Name); #if RELEASE - if (Log.IsEnabled(LogEventLevel.Debug)) - { - // TODO log level setting - Task.Run(() => Log.Debug("Third-party Drivers:\n{Drivers}", string.Join(Constants.EOF, SystemInfo.SystemDrivers(false)))).Forget(); - Task.Run(() => Log.Debug("Running Processes: \n{Processes}", string.Join(Constants.EOF, SystemInfo.Processes(false)))).Forget(); - } + if (Log.IsEnabled(LogEventLevel.Debug)) + { + // TODO log level setting + Task.Run(() => Log.Debug("Third-party Drivers:\n{Drivers}", string.Join(Constants.EOF, SystemInfo.SystemDrivers(false)))).Forget(); + Task.Run(() => Log.Debug("Running Processes: \n{Processes}", string.Join(Constants.EOF, SystemInfo.Processes(false)))).Forget(); + } #endif + } + + private static void CheckClr() + { + var framework = Assembly.GetExecutingAssembly().GetCustomAttribute()?.FrameworkName; + if (framework == null) + { + Log.Warning("TargetFrameworkAttribute null"); + return; } - private static void CheckClr() + var frameworkName = new FrameworkName(framework); + + if (frameworkName.Version.Major != Environment.Version.Major) { - var framework = Assembly.GetExecutingAssembly().GetCustomAttribute()?.FrameworkName; - if (framework == null) - { - Log.Warning("TargetFrameworkAttribute null"); - return; - } - - var frameworkName = new FrameworkName(framework); - - if (frameworkName.Version.Major != Environment.Version.Major) - { - Log.Information("CLR: {Version}", Environment.Version); - Flags.NoSupport = true; - if (!Global.Settings.NoSupportDialog) - MessageBoxX.Show( - i18N.TranslateFormat("{0} won't get developers' support, Please do not report any issues or seek help from developers.", - "CLR " + Environment.Version), - LogLevel.WARNING); - } - } - - private static void CheckOS() - { - if (Environment.OSVersion.Version.Build < 17763) - { - Flags.NoSupport = true; - if (!Global.Settings.NoSupportDialog) - MessageBoxX.Show( - i18N.TranslateFormat("{0} won't get developers' support, Please do not report any issues or seek help from developers.", - Environment.OSVersion), - LogLevel.WARNING); - } - } - - private static void InitConsole() - { - PInvoke.AllocConsole(); - - ConsoleHwnd = PInvoke.GetConsoleWindow(); -#if RELEASE - // hide console window - PInvoke.ShowWindow(ConsoleHwnd, SHOW_WINDOW_CMD.SW_HIDE); -#endif - } - - public static void CreateLogger() - { - Log.Logger = new LoggerConfiguration() -#if DEBUG - .MinimumLevel.Verbose() -#else - .MinimumLevel.Debug() -#endif - .WriteTo.Async(c => c.File(Path.Combine(Global.NetchDir, Constants.LogFile), - outputTemplate: Constants.OutputTemplate, - rollOnFileSizeLimit: false)) - .WriteTo.Console(outputTemplate: Constants.OutputTemplate) - .MinimumLevel.Override(@"Microsoft", LogEventLevel.Information) - .Enrich.FromLogContext() - .CreateLogger(); - } - - private static void Application_OnException(object sender, ThreadExceptionEventArgs e) - { - Log.Error(e.Exception, "Unhandled error"); - } - - private static void Application_OnExit(object? sender, EventArgs eventArgs) - { - Log.CloseAndFlush(); - } - - private static void SingleInstance_ArgumentsReceived((string, Action) receive) - { - var (arg, endFunc) = receive; - if (arg == Constants.Parameter.Show) - { - Utils.Utils.ActivateVisibleWindows(); - } - - endFunc(string.Empty); + Log.Information("CLR: {Version}", Environment.Version); + Flags.NoSupport = true; + if (!Global.Settings.NoSupportDialog) + MessageBoxX.Show( + i18N.TranslateFormat("{0} won't get developers' support, Please do not report any issues or seek help from developers.", + "CLR " + Environment.Version), + LogLevel.WARNING); } } + + private static void CheckOS() + { + if (Environment.OSVersion.Version.Build < 17763) + { + Flags.NoSupport = true; + if (!Global.Settings.NoSupportDialog) + MessageBoxX.Show( + i18N.TranslateFormat("{0} won't get developers' support, Please do not report any issues or seek help from developers.", + Environment.OSVersion), + LogLevel.WARNING); + } + } + + private static void InitConsole() + { + PInvoke.AllocConsole(); + + ConsoleHwnd = PInvoke.GetConsoleWindow(); +#if RELEASE + // hide console window + PInvoke.ShowWindow(ConsoleHwnd, SHOW_WINDOW_CMD.SW_HIDE); +#endif + } + + public static void CreateLogger() + { + Log.Logger = new LoggerConfiguration() +#if DEBUG + .MinimumLevel.Verbose() +#else + .MinimumLevel.Debug() +#endif + .WriteTo.Async(c => c.File(Path.Combine(Global.NetchDir, Constants.LogFile), + outputTemplate: Constants.OutputTemplate, + rollOnFileSizeLimit: false)) + .WriteTo.Console(outputTemplate: Constants.OutputTemplate) + .MinimumLevel.Override(@"Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .CreateLogger(); + } + + private static void Application_OnException(object sender, ThreadExceptionEventArgs e) + { + Log.Error(e.Exception, "Unhandled error"); + } + + private static void Application_OnExit(object? sender, EventArgs eventArgs) + { + Log.CloseAndFlush(); + } + + private static void SingleInstance_ArgumentsReceived((string, Action) receive) + { + var (arg, endFunc) = receive; + if (arg == Constants.Parameter.Show) + { + Utils.Utils.ActivateVisibleWindows(); + } + + endFunc(string.Empty); + } } \ No newline at end of file diff --git a/Netch/Servers/Shadowsocks/ShadowsocksController.cs b/Netch/Servers/Shadowsocks/ShadowsocksController.cs index 793378f9..ff02224e 100644 --- a/Netch/Servers/Shadowsocks/ShadowsocksController.cs +++ b/Netch/Servers/Shadowsocks/ShadowsocksController.cs @@ -1,46 +1,43 @@ -using System.Collections.Generic; using System.Net; using System.Text; -using System.Threading.Tasks; using Netch.Controllers; using Netch.Interfaces; using Netch.Models; -namespace Netch.Servers +namespace Netch.Servers; + +public class ShadowsocksController : Guard, IServerController { - public class ShadowsocksController : Guard, IServerController + public ShadowsocksController() : base("Shadowsocks.exe", encoding: Encoding.UTF8) { - public ShadowsocksController() : base("Shadowsocks.exe", encoding: Encoding.UTF8) + } + + protected override IEnumerable StartedKeywords => new[] { "listening on" }; + + protected override IEnumerable FailedKeywords => new[] { "error", "failed to start plguin" }; + + public override string Name => "Shadowsocks"; + + public ushort? Socks5LocalPort { get; set; } + + public string? LocalAddress { get; set; } + + public async Task StartAsync(Server s) + { + var server = (ShadowsocksServer)s; + + var arguments = new object?[] { - } + "-s", $"{await server.AutoResolveHostnameAsync()}:{server.Port}", + "-b", $"{this.LocalAddress()}:{this.Socks5LocalPort()}", + "-m", server.EncryptMethod, + "-k", server.Password, + "--plugin", server.Plugin, + "--plugin-opts", server.PluginOption, + "-U", SpecialArgument.Flag + }; - protected override IEnumerable StartedKeywords => new[] { "listening on" }; - - protected override IEnumerable FailedKeywords => new[] { "error", "failed to start plguin" }; - - public override string Name => "Shadowsocks"; - - public ushort? Socks5LocalPort { get; set; } - - public string? LocalAddress { get; set; } - - public async Task StartAsync(Server s) - { - var server = (ShadowsocksServer)s; - - var arguments = new object?[] - { - "-s", $"{await server.AutoResolveHostnameAsync()}:{server.Port}", - "-b", $"{this.LocalAddress()}:{this.Socks5LocalPort()}", - "-m", server.EncryptMethod, - "-k", server.Password, - "--plugin", server.Plugin, - "--plugin-opts", server.PluginOption, - "-U", SpecialArgument.Flag - }; - - await StartGuardAsync(Arguments.Format(arguments)); - return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname); - } + await StartGuardAsync(Arguments.Format(arguments)); + return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname); } } \ No newline at end of file diff --git a/Netch/Servers/Shadowsocks/ShadowsocksForm.cs b/Netch/Servers/Shadowsocks/ShadowsocksForm.cs index e1b69135..b5a23a16 100644 --- a/Netch/Servers/Shadowsocks/ShadowsocksForm.cs +++ b/Netch/Servers/Shadowsocks/ShadowsocksForm.cs @@ -1,20 +1,19 @@ using Netch.Forms; using Netch.Utils; -namespace Netch.Servers -{ - public class ShadowsocksForm : ServerForm - { - public ShadowsocksForm(ShadowsocksServer? server = default) - { - server ??= new ShadowsocksServer(); - Server = server; - CreateTextBox("Password", "Password", s => !s.IsNullOrWhiteSpace(), s => server.Password = s, server.Password); - CreateComboBox("EncryptMethod", "Encrypt Method", SSGlobal.EncryptMethods, s => server.EncryptMethod = s, server.EncryptMethod); - CreateTextBox("Plugin", "Plugin", s => true, s => server.Plugin = s, server.Plugin); - CreateTextBox("PluginsOption", "Plugin Options", s => true, s => server.PluginOption = s, server.PluginOption); - } +namespace Netch.Servers; - protected override string TypeName { get; } = "Shadowsocks"; +public class ShadowsocksForm : ServerForm +{ + public ShadowsocksForm(ShadowsocksServer? server = default) + { + server ??= new ShadowsocksServer(); + Server = server; + CreateTextBox("Password", "Password", s => !s.IsNullOrWhiteSpace(), s => server.Password = s, server.Password); + CreateComboBox("EncryptMethod", "Encrypt Method", SSGlobal.EncryptMethods, s => server.EncryptMethod = s, server.EncryptMethod); + CreateTextBox("Plugin", "Plugin", s => true, s => server.Plugin = s, server.Plugin); + CreateTextBox("PluginsOption", "Plugin Options", s => true, s => server.PluginOption = s, server.PluginOption); } + + protected override string TypeName { get; } = "Shadowsocks"; } \ No newline at end of file diff --git a/Netch/Servers/Shadowsocks/ShadowsocksServer.cs b/Netch/Servers/Shadowsocks/ShadowsocksServer.cs index 1d8ed8f2..ff4c9979 100644 --- a/Netch/Servers/Shadowsocks/ShadowsocksServer.cs +++ b/Netch/Servers/Shadowsocks/ShadowsocksServer.cs @@ -1,68 +1,66 @@ -using System.Collections.Generic; -using Netch.Models; +using Netch.Models; -namespace Netch.Servers +namespace Netch.Servers; + +public class ShadowsocksServer : Server { - public class ShadowsocksServer : Server + public override string Type { get; } = "SS"; + public override string MaskedData() { - public override string Type { get; } = "SS"; - public override string MaskedData() - { - return $"{EncryptMethod} + {Plugin}"; - } - - /// - /// 加密方式 - /// - public string EncryptMethod { get; set; } = SSGlobal.EncryptMethods[0]; - - /// - /// 密码 - /// - public string Password { get; set; } = string.Empty; - - /// - /// 插件 - /// - public string? Plugin { get; set; } - - /// - /// 插件参数 - /// - public string? PluginOption { get; set; } - - public bool HasPlugin() - { - return !string.IsNullOrWhiteSpace(Plugin) && !string.IsNullOrWhiteSpace(PluginOption); - } + return $"{EncryptMethod} + {Plugin}"; } - public static class SSGlobal + /// + /// 加密方式 + /// + public string EncryptMethod { get; set; } = SSGlobal.EncryptMethods[0]; + + /// + /// 密码 + /// + public string Password { get; set; } = string.Empty; + + /// + /// 插件 + /// + public string? Plugin { get; set; } + + /// + /// 插件参数 + /// + public string? PluginOption { get; set; } + + public bool HasPlugin() { - /// - /// SS 加密列表 - /// - public static readonly List EncryptMethods = new() - { - "rc4-md5", - "aes-128-gcm", - "aes-192-gcm", - "aes-256-gcm", - "aes-128-cfb", - "aes-192-cfb", - "aes-256-cfb", - "aes-128-ctr", - "aes-192-ctr", - "aes-256-ctr", - "camellia-128-cfb", - "camellia-192-cfb", - "camellia-256-cfb", - "bf-cfb", - "chacha20-ietf-poly1305", - "xchacha20-ietf-poly1305", - "salsa20", - "chacha20", - "chacha20-ietf" - }; + return !string.IsNullOrWhiteSpace(Plugin) && !string.IsNullOrWhiteSpace(PluginOption); } +} + +public static class SSGlobal +{ + /// + /// SS 加密列表 + /// + public static readonly List EncryptMethods = new() + { + "rc4-md5", + "aes-128-gcm", + "aes-192-gcm", + "aes-256-gcm", + "aes-128-cfb", + "aes-192-cfb", + "aes-256-cfb", + "aes-128-ctr", + "aes-192-ctr", + "aes-256-ctr", + "camellia-128-cfb", + "camellia-192-cfb", + "camellia-256-cfb", + "bf-cfb", + "chacha20-ietf-poly1305", + "xchacha20-ietf-poly1305", + "salsa20", + "chacha20", + "chacha20-ietf" + }; } \ No newline at end of file diff --git a/Netch/Servers/Shadowsocks/ShadowsocksUtil.cs b/Netch/Servers/Shadowsocks/ShadowsocksUtil.cs index 1a464e8c..1331ebeb 100644 --- a/Netch/Servers/Shadowsocks/ShadowsocksUtil.cs +++ b/Netch/Servers/Shadowsocks/ShadowsocksUtil.cs @@ -1,176 +1,171 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; +using System.Text.Json; using System.Text.RegularExpressions; using System.Web; using Netch.Interfaces; using Netch.Models; using Netch.Utils; -using Serilog; -namespace Netch.Servers +namespace Netch.Servers; + +public class ShadowsocksUtil : IServerUtil { - public class ShadowsocksUtil : IServerUtil + public ushort Priority { get; } = 1; + + public string TypeName { get; } = "SS"; + + public string FullName { get; } = "Shadowsocks"; + + public string ShortName { get; } = "SS"; + + public string[] UriScheme { get; } = { "ss", "ssd" }; + + public Type ServerType { get; } = typeof(ShadowsocksServer); + + public void Edit(Server s) { - public ushort Priority { get; } = 1; + new ShadowsocksForm((ShadowsocksServer)s).ShowDialog(); + } - public string TypeName { get; } = "SS"; + public void Create() + { + new ShadowsocksForm().ShowDialog(); + } - public string FullName { get; } = "Shadowsocks"; + public string GetShareLink(Server s) + { + var server = (ShadowsocksServer)s; + // ss://method:password@server:port#Remark + return "ss://" + ShareLink.URLSafeBase64Encode($"{server.EncryptMethod}:{server.Password}@{server.Hostname}:{server.Port}") + "#" + + HttpUtility.UrlEncode(server.Remark); + } - public string ShortName { get; } = "SS"; + public IServerController GetController() + { + return new ShadowsocksController(); + } - public string[] UriScheme { get; } = { "ss", "ssd" }; + public IEnumerable ParseUri(string text) + { + if (text.StartsWith("ss://")) + return new[] { ParseSsUri(text) }; - public Type ServerType { get; } = typeof(ShadowsocksServer); + if (text.StartsWith("ssd://")) + return ParseSsdUri(text); - public void Edit(Server s) + throw new FormatException(); + } + + public bool CheckServer(Server s) + { + var server = (ShadowsocksServer)s; + if (!SSGlobal.EncryptMethods.Contains(server.EncryptMethod)) { - new ShadowsocksForm((ShadowsocksServer)s).ShowDialog(); + Log.Warning("Unsupported SS Encrypt Method: {Method}", server.EncryptMethod); + return false; } - public void Create() - { - new ShadowsocksForm().ShowDialog(); - } + return true; + } - public string GetShareLink(Server s) - { - var server = (ShadowsocksServer)s; - // ss://method:password@server:port#Remark - return "ss://" + ShareLink.URLSafeBase64Encode($"{server.EncryptMethod}:{server.Password}@{server.Hostname}:{server.Port}") + "#" + - HttpUtility.UrlEncode(server.Remark); - } + public IEnumerable ParseSsdUri(string s) + { + var json = JsonSerializer.Deserialize(ShareLink.URLSafeBase64Decode(s.Substring(6)))!; - public IServerController GetController() - { - return new ShadowsocksController(); - } - - public IEnumerable ParseUri(string text) - { - if (text.StartsWith("ss://")) - return new[] { ParseSsUri(text) }; - - if (text.StartsWith("ssd://")) - return ParseSsdUri(text); - - throw new FormatException(); - } - - public bool CheckServer(Server s) - { - var server = (ShadowsocksServer)s; - if (!SSGlobal.EncryptMethods.Contains(server.EncryptMethod)) + return json.servers.Select(server => new ShadowsocksServer { - Log.Warning("Unsupported SS Encrypt Method: {Method}", server.EncryptMethod); - return false; - } + Remark = server.remarks, + Hostname = server.server, + Port = server.port != 0 ? server.port : json.port, + Password = server.password ?? json.password, + EncryptMethod = server.encryption ?? json.encryption, + Plugin = string.IsNullOrEmpty(json.plugin) ? string.IsNullOrEmpty(server.plugin) ? null : server.plugin : json.plugin, + PluginOption = string.IsNullOrEmpty(json.plugin_options) + ? string.IsNullOrEmpty(server.plugin_options) ? null : server.plugin_options + : json.plugin_options + }) + .Where(CheckServer); + } - return true; + public ShadowsocksServer ParseSsUri(string text) + { + var data = new ShadowsocksServer(); + + text = text.Replace("/?", "?"); + if (text.Contains("#")) + { + data.Remark = HttpUtility.UrlDecode(text.Split('#')[1]); + text = text.Split('#')[0]; } - public IEnumerable ParseSsdUri(string s) + if (text.Contains("?")) { - var json = JsonSerializer.Deserialize(ShareLink.URLSafeBase64Decode(s.Substring(6)))!; + var finder = new Regex(@"^(?.+?)\?(.+)$"); + var match = finder.Match(text); - return json.servers.Select(server => new ShadowsocksServer + if (!match.Success) + throw new FormatException(); + + var plugins = HttpUtility.UrlDecode(HttpUtility.ParseQueryString(new Uri(text).Query).Get("plugin")); + if (plugins != null) + { + var plugin = plugins.Substring(0, plugins.IndexOf(";", StringComparison.Ordinal)); + var pluginopts = plugins.Substring(plugins.IndexOf(";", StringComparison.Ordinal) + 1); + switch (plugin) { - Remark = server.remarks, - Hostname = server.server, - Port = server.port != 0 ? server.port : json.port, - Password = server.password ?? json.password, - EncryptMethod = server.encryption ?? json.encryption, - Plugin = string.IsNullOrEmpty(json.plugin) ? string.IsNullOrEmpty(server.plugin) ? null : server.plugin : json.plugin, - PluginOption = string.IsNullOrEmpty(json.plugin_options) - ? string.IsNullOrEmpty(server.plugin_options) ? null : server.plugin_options - : json.plugin_options - }) - .Where(CheckServer); - } + case "obfs-local": + case "simple-obfs": + plugin = "simple-obfs"; + if (!pluginopts.Contains("obfs=")) + pluginopts = "obfs=http;obfs-host=" + pluginopts; - public ShadowsocksServer ParseSsUri(string text) - { - var data = new ShadowsocksServer(); + break; + case "simple-obfs-tls": + plugin = "simple-obfs"; + if (!pluginopts.Contains("obfs=")) + pluginopts = "obfs=tls;obfs-host=" + pluginopts; - text = text.Replace("/?", "?"); - if (text.Contains("#")) - { - data.Remark = HttpUtility.UrlDecode(text.Split('#')[1]); - text = text.Split('#')[0]; - } - - if (text.Contains("?")) - { - var finder = new Regex(@"^(?.+?)\?(.+)$"); - var match = finder.Match(text); - - if (!match.Success) - throw new FormatException(); - - var plugins = HttpUtility.UrlDecode(HttpUtility.ParseQueryString(new Uri(text).Query).Get("plugin")); - if (plugins != null) - { - var plugin = plugins.Substring(0, plugins.IndexOf(";", StringComparison.Ordinal)); - var pluginopts = plugins.Substring(plugins.IndexOf(";", StringComparison.Ordinal) + 1); - switch (plugin) - { - case "obfs-local": - case "simple-obfs": - plugin = "simple-obfs"; - if (!pluginopts.Contains("obfs=")) - pluginopts = "obfs=http;obfs-host=" + pluginopts; - - break; - case "simple-obfs-tls": - plugin = "simple-obfs"; - if (!pluginopts.Contains("obfs=")) - pluginopts = "obfs=tls;obfs-host=" + pluginopts; - - break; - } - - data.Plugin = plugin; - data.PluginOption = pluginopts; + break; } - text = match.Groups["data"].Value; + data.Plugin = plugin; + data.PluginOption = pluginopts; } - if (text.Contains("@")) - { - var finder = new Regex(@"^ss://(?.+?)@(?.+):(?\d+)"); - var parser = new Regex(@"^(?.+?):(?.+)$"); - var match = finder.Match(text); - if (!match.Success) - throw new FormatException(); - - data.Hostname = match.Groups["server"].Value; - data.Port = ushort.Parse(match.Groups["port"].Value); - - var base64 = ShareLink.URLSafeBase64Decode(match.Groups["base64"].Value); - match = parser.Match(base64); - if (!match.Success) - throw new FormatException(); - - data.EncryptMethod = match.Groups["method"].Value; - data.Password = match.Groups["password"].Value; - } - else - { - var parser = new Regex(@"^((?.+?):(?.+)@(?.+):(?\d+))"); - var match = parser.Match(ShareLink.URLSafeBase64Decode(text.Replace("ss://", ""))); - if (!match.Success) - throw new FormatException(); - - data.Hostname = match.Groups["server"].Value; - data.Port = ushort.Parse(match.Groups["port"].Value); - data.EncryptMethod = match.Groups["method"].Value; - data.Password = match.Groups["password"].Value; - } - - return CheckServer(data) ? data : throw new FormatException(); + text = match.Groups["data"].Value; } + + if (text.Contains("@")) + { + var finder = new Regex(@"^ss://(?.+?)@(?.+):(?\d+)"); + var parser = new Regex(@"^(?.+?):(?.+)$"); + var match = finder.Match(text); + if (!match.Success) + throw new FormatException(); + + data.Hostname = match.Groups["server"].Value; + data.Port = ushort.Parse(match.Groups["port"].Value); + + var base64 = ShareLink.URLSafeBase64Decode(match.Groups["base64"].Value); + match = parser.Match(base64); + if (!match.Success) + throw new FormatException(); + + data.EncryptMethod = match.Groups["method"].Value; + data.Password = match.Groups["password"].Value; + } + else + { + var parser = new Regex(@"^((?.+?):(?.+)@(?.+):(?\d+))"); + var match = parser.Match(ShareLink.URLSafeBase64Decode(text.Replace("ss://", ""))); + if (!match.Success) + throw new FormatException(); + + data.Hostname = match.Groups["server"].Value; + data.Port = ushort.Parse(match.Groups["port"].Value); + data.EncryptMethod = match.Groups["method"].Value; + data.Password = match.Groups["password"].Value; + } + + return CheckServer(data) ? data : throw new FormatException(); } } \ No newline at end of file diff --git a/Netch/Servers/Shadowsocks/ShareModels/SSDJObject.cs b/Netch/Servers/Shadowsocks/ShareModels/SSDJObject.cs index 69adeb65..fa86c6d9 100644 --- a/Netch/Servers/Shadowsocks/ShareModels/SSDJObject.cs +++ b/Netch/Servers/Shadowsocks/ShareModels/SSDJObject.cs @@ -1,43 +1,40 @@ #nullable disable -using System.Collections.Generic; +namespace Netch.Servers; -namespace Netch.Servers +public class SSDJObject { - public class SSDJObject - { - /// - /// 机场名 - /// - public string airport; + /// + /// 机场名 + /// + public string airport; - /// - /// 加密方式 - /// - public string encryption; + /// + /// 加密方式 + /// + public string encryption; - /// - /// 密码 - /// - public string password; + /// + /// 密码 + /// + public string password; - /// - /// 插件 - /// - public string plugin; + /// + /// 插件 + /// + public string plugin; - /// - /// 插件参数 - /// - public string plugin_options; + /// + /// 插件参数 + /// + public string plugin_options; - /// - /// 端口 - /// - public ushort port; + /// + /// 端口 + /// + public ushort port; - /// - /// 服务器数组 - /// - public List servers; - } + /// + /// 服务器数组 + /// + public List servers; } \ No newline at end of file diff --git a/Netch/Servers/Shadowsocks/ShareModels/SSDServerJObject.cs b/Netch/Servers/Shadowsocks/ShareModels/SSDServerJObject.cs index 5807e379..3a1f4821 100644 --- a/Netch/Servers/Shadowsocks/ShareModels/SSDServerJObject.cs +++ b/Netch/Servers/Shadowsocks/ShareModels/SSDServerJObject.cs @@ -1,40 +1,39 @@ #nullable disable -namespace Netch.Servers +namespace Netch.Servers; + +public class SSDServerJObject { - public class SSDServerJObject - { - /// - /// 加密方式 - /// - public string encryption; + /// + /// 加密方式 + /// + public string encryption; - /// - /// 密码 - /// - public string password; + /// + /// 密码 + /// + public string password; - /// - /// 插件 - /// - public string plugin; + /// + /// 插件 + /// + public string plugin; - /// - /// 插件参数 - /// - public string plugin_options; + /// + /// 插件参数 + /// + public string plugin_options; - /// - /// 端口 - /// - public ushort port; + /// + /// 端口 + /// + public ushort port; - /// - /// 备注 - /// - public string remarks; - /// - /// 服务器地址 - /// - public string server; - } + /// + /// 备注 + /// + public string remarks; + /// + /// 服务器地址 + /// + public string server; } \ No newline at end of file diff --git a/Netch/Servers/Shadowsocks/ShareModels/ShadowsocksConfig.cs b/Netch/Servers/Shadowsocks/ShareModels/ShadowsocksConfig.cs index 6cc3179f..b70ce3d4 100644 --- a/Netch/Servers/Shadowsocks/ShareModels/ShadowsocksConfig.cs +++ b/Netch/Servers/Shadowsocks/ShareModels/ShadowsocksConfig.cs @@ -1,24 +1,23 @@ #nullable disable -namespace Netch.Servers +namespace Netch.Servers; + +/// +/// Import Shadowsocks Server from Json Configuration +/// +/// +public class ShadowsocksConfig { - /// - /// Import Shadowsocks Server from Json Configuration - /// - /// - public class ShadowsocksConfig - { - public string server { get; set; } + public string server { get; set; } - public ushort server_port { get; set; } + public ushort server_port { get; set; } - public string password { get; set; } + public string password { get; set; } - public string method { get; set; } + public string method { get; set; } - public string remarks { get; set; } + public string remarks { get; set; } - public string plugin { get; set; } + public string plugin { get; set; } - public string plugin_opts { get; set; } - } + public string plugin_opts { get; set; } } \ No newline at end of file diff --git a/Netch/Servers/ShadowsocksR/ShadowsocksRController.cs b/Netch/Servers/ShadowsocksR/ShadowsocksRController.cs index 52dcaf94..8ea654f2 100644 --- a/Netch/Servers/ShadowsocksR/ShadowsocksRController.cs +++ b/Netch/Servers/ShadowsocksR/ShadowsocksRController.cs @@ -1,50 +1,47 @@ -using System.Collections.Generic; using System.Net; -using System.Threading.Tasks; using Netch.Controllers; using Netch.Interfaces; using Netch.Models; -namespace Netch.Servers +namespace Netch.Servers; + +public class ShadowsocksRController : Guard, IServerController { - public class ShadowsocksRController : Guard, IServerController + public ShadowsocksRController() : base("ShadowsocksR.exe") { - public ShadowsocksRController() : base("ShadowsocksR.exe") + } + + protected override IEnumerable StartedKeywords => new[] { "listening at" }; + + protected override IEnumerable FailedKeywords => new[] { "Invalid config path", "usage" }; + + public override string Name => "ShadowsocksR"; + + public ushort? Socks5LocalPort { get; set; } + + public string? LocalAddress { get; set; } + + public async Task StartAsync(Server s) + { + var server = (ShadowsocksRServer)s; + + var arguments = new object?[] { - } + "-s", await server.AutoResolveHostnameAsync(), + "-p", server.Port, + "-k", server.Password, + "-m", server.EncryptMethod, + "-t", 120, + "-O", server.Protocol, + "-G", server.ProtocolParam, + "-o", server.OBFS, + "-g", server.OBFSParam, + "-b", this.LocalAddress(), + "-l", this.Socks5LocalPort(), + "-u", SpecialArgument.Flag + }; - protected override IEnumerable StartedKeywords => new[] { "listening at" }; - - protected override IEnumerable FailedKeywords => new[] { "Invalid config path", "usage" }; - - public override string Name => "ShadowsocksR"; - - public ushort? Socks5LocalPort { get; set; } - - public string? LocalAddress { get; set; } - - public async Task StartAsync(Server s) - { - var server = (ShadowsocksRServer)s; - - var arguments = new object?[] - { - "-s", await server.AutoResolveHostnameAsync(), - "-p", server.Port, - "-k", server.Password, - "-m", server.EncryptMethod, - "-t", 120, - "-O", server.Protocol, - "-G", server.ProtocolParam, - "-o", server.OBFS, - "-g", server.OBFSParam, - "-b", this.LocalAddress(), - "-l", this.Socks5LocalPort(), - "-u", SpecialArgument.Flag - }; - - await StartGuardAsync(Arguments.Format(arguments)); - return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname); - } + await StartGuardAsync(Arguments.Format(arguments)); + return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname); } } \ No newline at end of file diff --git a/Netch/Servers/ShadowsocksR/ShadowsocksRForm.cs b/Netch/Servers/ShadowsocksR/ShadowsocksRForm.cs index 3352a391..8c92ff06 100644 --- a/Netch/Servers/ShadowsocksR/ShadowsocksRForm.cs +++ b/Netch/Servers/ShadowsocksR/ShadowsocksRForm.cs @@ -1,22 +1,21 @@ using Netch.Forms; using Netch.Utils; -namespace Netch.Servers -{ - public class ShadowsocksRForm : ServerForm - { - public ShadowsocksRForm(ShadowsocksRServer? server = default) - { - server ??= new ShadowsocksRServer(); - Server = server; - CreateTextBox("Password", "Password", s => !s.IsNullOrWhiteSpace(), s => server.Password = s, server.Password); - CreateComboBox("EncryptMethod", "Encrypt Method", SSRGlobal.EncryptMethods, s => server.EncryptMethod = s, server.EncryptMethod); - CreateComboBox("Protocol", "Protocol", SSRGlobal.Protocols, s => server.Protocol = s, server.Protocol); - CreateTextBox("ProtocolParam", "Protocol Param", s => true, s => server.ProtocolParam = s, server.ProtocolParam); - CreateComboBox("OBFS", "OBFS", SSRGlobal.OBFSs, s => server.OBFS = s, server.OBFS); - CreateTextBox("OBFSParam", "OBFS Param", s => true, s => server.OBFSParam = s, server.OBFSParam); - } +namespace Netch.Servers; - protected override string TypeName { get; } = "ShadowsocksR"; +public class ShadowsocksRForm : ServerForm +{ + public ShadowsocksRForm(ShadowsocksRServer? server = default) + { + server ??= new ShadowsocksRServer(); + Server = server; + CreateTextBox("Password", "Password", s => !s.IsNullOrWhiteSpace(), s => server.Password = s, server.Password); + CreateComboBox("EncryptMethod", "Encrypt Method", SSRGlobal.EncryptMethods, s => server.EncryptMethod = s, server.EncryptMethod); + CreateComboBox("Protocol", "Protocol", SSRGlobal.Protocols, s => server.Protocol = s, server.Protocol); + CreateTextBox("ProtocolParam", "Protocol Param", s => true, s => server.ProtocolParam = s, server.ProtocolParam); + CreateComboBox("OBFS", "OBFS", SSRGlobal.OBFSs, s => server.OBFS = s, server.OBFS); + CreateTextBox("OBFSParam", "OBFS Param", s => true, s => server.OBFSParam = s, server.OBFSParam); } + + protected override string TypeName { get; } = "ShadowsocksR"; } \ No newline at end of file diff --git a/Netch/Servers/ShadowsocksR/ShadowsocksRServer.cs b/Netch/Servers/ShadowsocksR/ShadowsocksRServer.cs index aa480c14..ba31f117 100644 --- a/Netch/Servers/ShadowsocksR/ShadowsocksRServer.cs +++ b/Netch/Servers/ShadowsocksR/ShadowsocksRServer.cs @@ -1,101 +1,99 @@ -using System.Collections.Generic; -using Netch.Models; +using Netch.Models; -namespace Netch.Servers +namespace Netch.Servers; + +public class ShadowsocksRServer : Server { - public class ShadowsocksRServer : Server + public override string Type { get; } = "SSR"; + public override string MaskedData() { - public override string Type { get; } = "SSR"; - public override string MaskedData() - { - return $"{EncryptMethod} + {Protocol} + {OBFS}"; - } - - /// - /// 密码 - /// - public string Password { get; set; } = string.Empty; - - /// - /// 加密方式 - /// - public string EncryptMethod { get; set; } = SSRGlobal.EncryptMethods[0]; - - /// - /// 协议 - /// - public string Protocol { get; set; } = SSRGlobal.Protocols[0]; - - /// - /// 协议参数 - /// - public string? ProtocolParam { get; set; } - - /// - /// 混淆 - /// - public string OBFS { get; set; } = SSRGlobal.OBFSs[0]; - - /// - /// 混淆参数 - /// - public string? OBFSParam { get; set; } + return $"{EncryptMethod} + {Protocol} + {OBFS}"; } - public class SSRGlobal + /// + /// 密码 + /// + public string Password { get; set; } = string.Empty; + + /// + /// 加密方式 + /// + public string EncryptMethod { get; set; } = SSRGlobal.EncryptMethods[0]; + + /// + /// 协议 + /// + public string Protocol { get; set; } = SSRGlobal.Protocols[0]; + + /// + /// 协议参数 + /// + public string? ProtocolParam { get; set; } + + /// + /// 混淆 + /// + public string OBFS { get; set; } = SSRGlobal.OBFSs[0]; + + /// + /// 混淆参数 + /// + public string? OBFSParam { get; set; } +} + +public class SSRGlobal +{ + /// + /// SSR 协议列表 + /// + public static readonly List Protocols = new() { - /// - /// SSR 协议列表 - /// - public static readonly List Protocols = new() - { - "origin", - "verify_deflate", - "auth_sha1_v4", - "auth_aes128_md5", - "auth_aes128_sha1", - "auth_chain_a" - }; + "origin", + "verify_deflate", + "auth_sha1_v4", + "auth_aes128_md5", + "auth_aes128_sha1", + "auth_chain_a" + }; - /// - /// SSR 混淆列表 - /// - public static readonly List OBFSs = new() - { - "plain", - "http_simple", - "http_post", - "tls1.2_ticket_auth" - }; + /// + /// SSR 混淆列表 + /// + public static readonly List OBFSs = new() + { + "plain", + "http_simple", + "http_post", + "tls1.2_ticket_auth" + }; - /// - /// SS/SSR 加密方式 - /// - public static readonly List EncryptMethods = new() - { - "none", - "table", - "rc4", - "rc4-md5", - "rc4-md5-6", - "aes-128-cfb", - "aes-192-cfb", - "aes-256-cfb", - "aes-128-ctr", - "aes-192-ctr", - "aes-256-ctr", - "bf-cfb", - "camellia-128-cfb", - "camellia-192-cfb", - "camellia-256-cfb", - "cast5-cfb", - "des-cfb", - "idea-cfb", - "rc2-cfb", - "seed-cfb", - "salsa20", - "chacha20", - "chacha20-ietf" - }; - } + /// + /// SS/SSR 加密方式 + /// + public static readonly List EncryptMethods = new() + { + "none", + "table", + "rc4", + "rc4-md5", + "rc4-md5-6", + "aes-128-cfb", + "aes-192-cfb", + "aes-256-cfb", + "aes-128-ctr", + "aes-192-ctr", + "aes-256-ctr", + "bf-cfb", + "camellia-128-cfb", + "camellia-192-cfb", + "camellia-256-cfb", + "cast5-cfb", + "des-cfb", + "idea-cfb", + "rc2-cfb", + "seed-cfb", + "salsa20", + "chacha20", + "chacha20-ietf" + }; } \ No newline at end of file diff --git a/Netch/Servers/ShadowsocksR/ShadowsocksRUtil.cs b/Netch/Servers/ShadowsocksR/ShadowsocksRUtil.cs index 8318536a..a9379c97 100644 --- a/Netch/Servers/ShadowsocksR/ShadowsocksRUtil.cs +++ b/Netch/Servers/ShadowsocksR/ShadowsocksRUtil.cs @@ -1,167 +1,163 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using Netch.Interfaces; using Netch.Models; using Netch.Utils; -using Serilog; -namespace Netch.Servers +namespace Netch.Servers; + +public class ShadowsocksRUtil : IServerUtil { - public class ShadowsocksRUtil : IServerUtil + public ushort Priority { get; } = 1; + + public string TypeName { get; } = "SSR"; + + public string FullName { get; } = "ShadowsocksR"; + + public string ShortName { get; } = "SR"; + + public string[] UriScheme { get; } = { "ssr" }; + + public Type ServerType { get; } = typeof(ShadowsocksRServer); + + public void Edit(Server s) { - public ushort Priority { get; } = 1; + new ShadowsocksRForm((ShadowsocksRServer)s).ShowDialog(); + } - public string TypeName { get; } = "SSR"; + public void Create() + { + new ShadowsocksRForm().ShowDialog(); + } - public string FullName { get; } = "ShadowsocksR"; + public string GetShareLink(Server s) + { + var server = (ShadowsocksRServer)s; - public string ShortName { get; } = "SR"; + // https://github.com/shadowsocksr-backup/shadowsocks-rss/wiki/SSR-QRcode-scheme + // ssr://base64(host:port:protocol:method:obfs:base64pass/?obfsparam=base64param&protoparam=base64param&remarks=base64remarks&group=base64group&udpport=0&uot=0) + var paraStr = + $"/?obfsparam={ShareLink.URLSafeBase64Encode(server.OBFSParam ?? "")}&protoparam={ShareLink.URLSafeBase64Encode(server.ProtocolParam ?? "")}&remarks={ShareLink.URLSafeBase64Encode(server.Remark)}"; - public string[] UriScheme { get; } = { "ssr" }; + return "ssr://" + + ShareLink.URLSafeBase64Encode( + $"{server.Hostname}:{server.Port}:{server.Protocol}:{server.EncryptMethod}:{server.OBFS}:{ShareLink.URLSafeBase64Encode(server.Password)}{paraStr}"); + } - public Type ServerType { get; } = typeof(ShadowsocksRServer); + public IServerController GetController() + { + return new ShadowsocksRController(); + } - public void Edit(Server s) + /// + /// SSR链接解析器 + /// Copy From + /// https://github.com/HMBSbige/ShadowsocksR-Windows/blob/d9dc8d032a6e04c14b9dc6c8f673c9cc5aa9f607/shadowsocks-csharp/Model/Server.cs#L428 + /// Thx :D + /// + /// + /// + public IEnumerable ParseUri(string text) + { + // ssr://host:port:protocol:method:obfs:base64pass/?obfsparam=base64&remarks=base64&group=base64&udpport=0&uot=1 + var ssr = Regex.Match(text, "ssr://([A-Za-z0-9_-]+)", RegexOptions.IgnoreCase); + if (!ssr.Success) + throw new FormatException(); + + var data = ShareLink.URLSafeBase64Decode(ssr.Groups[1].Value); + var paramsDict = new Dictionary(); + + var paramStartPos = data.IndexOf("?", StringComparison.Ordinal); + if (paramStartPos > 0) { - new ShadowsocksRForm((ShadowsocksRServer)s).ShowDialog(); + paramsDict = ShareLink.ParseParam(data.Substring(paramStartPos + 1)); + data = data.Substring(0, paramStartPos); } - public void Create() - { - new ShadowsocksRForm().ShowDialog(); - } + if (data.IndexOf("/", StringComparison.Ordinal) >= 0) + data = data.Substring(0, data.LastIndexOf("/", StringComparison.Ordinal)); - public string GetShareLink(Server s) - { - var server = (ShadowsocksRServer)s; + var urlFinder = new Regex("^(.+):([^:]+):([^:]*):([^:]+):([^:]*):([^:]+)"); + var match = urlFinder.Match(data); - // https://github.com/shadowsocksr-backup/shadowsocks-rss/wiki/SSR-QRcode-scheme - // ssr://base64(host:port:protocol:method:obfs:base64pass/?obfsparam=base64param&protoparam=base64param&remarks=base64remarks&group=base64group&udpport=0&uot=0) - var paraStr = - $"/?obfsparam={ShareLink.URLSafeBase64Encode(server.OBFSParam ?? "")}&protoparam={ShareLink.URLSafeBase64Encode(server.ProtocolParam ?? "")}&remarks={ShareLink.URLSafeBase64Encode(server.Remark)}"; + if (match == null || !match.Success) + throw new FormatException(); - return "ssr://" + - ShareLink.URLSafeBase64Encode( - $"{server.Hostname}:{server.Port}:{server.Protocol}:{server.EncryptMethod}:{server.OBFS}:{ShareLink.URLSafeBase64Encode(server.Password)}{paraStr}"); - } + var serverAddr = match.Groups[1].Value; + var serverPort = ushort.Parse(match.Groups[2].Value); + var protocol = match.Groups[3].Value.Length == 0 ? "origin" : match.Groups[3].Value; + protocol = protocol.Replace("_compatible", ""); + var method = match.Groups[4].Value; + var obfs = match.Groups[5].Value.Length == 0 ? "plain" : match.Groups[5].Value; + obfs = obfs.Replace("_compatible", ""); + var password = ShareLink.URLSafeBase64Decode(match.Groups[6].Value); + var protocolParam = ""; + var obfsParam = ""; + var remarks = ""; - public IServerController GetController() - { - return new ShadowsocksRController(); - } + if (paramsDict.ContainsKey("protoparam")) + protocolParam = ShareLink.URLSafeBase64Decode(paramsDict["protoparam"]); - /// - /// SSR链接解析器 - /// Copy From - /// https://github.com/HMBSbige/ShadowsocksR-Windows/blob/d9dc8d032a6e04c14b9dc6c8f673c9cc5aa9f607/shadowsocks-csharp/Model/Server.cs#L428 - /// Thx :D - /// - /// - /// - public IEnumerable ParseUri(string text) - { - // ssr://host:port:protocol:method:obfs:base64pass/?obfsparam=base64&remarks=base64&group=base64&udpport=0&uot=1 - var ssr = Regex.Match(text, "ssr://([A-Za-z0-9_-]+)", RegexOptions.IgnoreCase); - if (!ssr.Success) - throw new FormatException(); + if (paramsDict.ContainsKey("obfsparam")) + obfsParam = ShareLink.URLSafeBase64Decode(paramsDict["obfsparam"]); - var data = ShareLink.URLSafeBase64Decode(ssr.Groups[1].Value); - var paramsDict = new Dictionary(); + if (paramsDict.ContainsKey("remarks")) + remarks = ShareLink.URLSafeBase64Decode(paramsDict["remarks"]); - var paramStartPos = data.IndexOf("?", StringComparison.Ordinal); - if (paramStartPos > 0) - { - paramsDict = ShareLink.ParseParam(data.Substring(paramStartPos + 1)); - data = data.Substring(0, paramStartPos); - } - - if (data.IndexOf("/", StringComparison.Ordinal) >= 0) - data = data.Substring(0, data.LastIndexOf("/", StringComparison.Ordinal)); - - var urlFinder = new Regex("^(.+):([^:]+):([^:]*):([^:]+):([^:]*):([^:]+)"); - var match = urlFinder.Match(data); - - if (match == null || !match.Success) - throw new FormatException(); - - var serverAddr = match.Groups[1].Value; - var serverPort = ushort.Parse(match.Groups[2].Value); - var protocol = match.Groups[3].Value.Length == 0 ? "origin" : match.Groups[3].Value; - protocol = protocol.Replace("_compatible", ""); - var method = match.Groups[4].Value; - var obfs = match.Groups[5].Value.Length == 0 ? "plain" : match.Groups[5].Value; - obfs = obfs.Replace("_compatible", ""); - var password = ShareLink.URLSafeBase64Decode(match.Groups[6].Value); - var protocolParam = ""; - var obfsParam = ""; - var remarks = ""; - - if (paramsDict.ContainsKey("protoparam")) - protocolParam = ShareLink.URLSafeBase64Decode(paramsDict["protoparam"]); - - if (paramsDict.ContainsKey("obfsparam")) - obfsParam = ShareLink.URLSafeBase64Decode(paramsDict["obfsparam"]); - - if (paramsDict.ContainsKey("remarks")) - remarks = ShareLink.URLSafeBase64Decode(paramsDict["remarks"]); - - var group = paramsDict.ContainsKey("group") ? ShareLink.URLSafeBase64Decode(paramsDict["group"]) : string.Empty; - - if (SSGlobal.EncryptMethods.Contains(method) && protocol == "origin" && obfs == "plain") - return new[] - { - new ShadowsocksServer - { - Hostname = serverAddr, - Port = serverPort, - EncryptMethod = method, - Password = password, - Remark = remarks, - Group = group - } - }; + var group = paramsDict.ContainsKey("group") ? ShareLink.URLSafeBase64Decode(paramsDict["group"]) : string.Empty; + if (SSGlobal.EncryptMethods.Contains(method) && protocol == "origin" && obfs == "plain") return new[] { - new ShadowsocksRServer + new ShadowsocksServer { Hostname = serverAddr, Port = serverPort, - Protocol = protocol, EncryptMethod = method, - OBFS = obfs, Password = password, - ProtocolParam = protocolParam, - OBFSParam = obfsParam, Remark = remarks, Group = group } }; - } - public bool CheckServer(Server s) + return new[] { - var server = (ShadowsocksRServer)s; - if (!SSRGlobal.EncryptMethods.Contains(server.EncryptMethod)) + new ShadowsocksRServer { - Log.Error("Unsupported ShadowsocksR Encrypt method: {Method}", server.EncryptMethod); - return false; + Hostname = serverAddr, + Port = serverPort, + Protocol = protocol, + EncryptMethod = method, + OBFS = obfs, + Password = password, + ProtocolParam = protocolParam, + OBFSParam = obfsParam, + Remark = remarks, + Group = group } + }; + } - if (!SSRGlobal.Protocols.Contains(server.Protocol)) - { - Log.Error("Unsupported ShadowsocksR Protocol: {Protocol}", server.Protocol); - return false; - } - - if (!SSRGlobal.OBFSs.Contains(server.OBFS)) - { - Log.Error("Unsupported ShadowsocksR Obfs: {Obfs}", server.OBFS); - return false; - } - - return true; + public bool CheckServer(Server s) + { + var server = (ShadowsocksRServer)s; + if (!SSRGlobal.EncryptMethods.Contains(server.EncryptMethod)) + { + Log.Error("Unsupported ShadowsocksR Encrypt method: {Method}", server.EncryptMethod); + return false; } + + if (!SSRGlobal.Protocols.Contains(server.Protocol)) + { + Log.Error("Unsupported ShadowsocksR Protocol: {Protocol}", server.Protocol); + return false; + } + + if (!SSRGlobal.OBFSs.Contains(server.OBFS)) + { + Log.Error("Unsupported ShadowsocksR Obfs: {Obfs}", server.OBFS); + return false; + } + + return true; } } \ No newline at end of file diff --git a/Netch/Servers/Socks5/Socks5Controller.cs b/Netch/Servers/Socks5/Socks5Controller.cs index aedf5fc8..89fffd6a 100644 --- a/Netch/Servers/Socks5/Socks5Controller.cs +++ b/Netch/Servers/Socks5/Socks5Controller.cs @@ -1,20 +1,17 @@ -using System; -using System.Threading.Tasks; using Netch.Models; -namespace Netch.Servers +namespace Netch.Servers; + +public class Socks5Controller : V2rayController { - public class Socks5Controller : V2rayController + public override string Name { get; } = "Socks5"; + + public override async Task StartAsync(Server s) { - public override string Name { get; } = "Socks5"; + var server = (Socks5Server)s; + if (!server.Auth()) + throw new ArgumentException(); - public override async Task StartAsync(Server s) - { - var server = (Socks5Server)s; - if (!server.Auth()) - throw new ArgumentException(); - - return await base.StartAsync(s); - } + return await base.StartAsync(s); } } \ No newline at end of file diff --git a/Netch/Servers/Socks5/Socks5Form.cs b/Netch/Servers/Socks5/Socks5Form.cs index d8ff241e..a9f86571 100644 --- a/Netch/Servers/Socks5/Socks5Form.cs +++ b/Netch/Servers/Socks5/Socks5Form.cs @@ -1,17 +1,16 @@ using Netch.Forms; -namespace Netch.Servers -{ - public class Socks5Form : ServerForm - { - public Socks5Form(Socks5Server? server = default) - { - server ??= new Socks5Server(); - Server = server; - CreateTextBox("Username", "Username", s => true, s => server.Username = s, server.Username); - CreateTextBox("Password", "Password", s => true, s => server.Password = s, server.Password); - } +namespace Netch.Servers; - protected override string TypeName { get; } = "Socks5"; +public class Socks5Form : ServerForm +{ + public Socks5Form(Socks5Server? server = default) + { + server ??= new Socks5Server(); + Server = server; + CreateTextBox("Username", "Username", s => true, s => server.Username = s, server.Username); + CreateTextBox("Password", "Password", s => true, s => server.Password = s, server.Password); } + + protected override string TypeName { get; } = "Socks5"; } \ No newline at end of file diff --git a/Netch/Servers/Socks5/Socks5LocalServer.cs b/Netch/Servers/Socks5/Socks5LocalServer.cs index 2fccb714..4eb360f9 100644 --- a/Netch/Servers/Socks5/Socks5LocalServer.cs +++ b/Netch/Servers/Socks5/Socks5LocalServer.cs @@ -1,19 +1,18 @@ -namespace Netch.Servers -{ - // TODO rename it - /// - /// Encrypted proxy client's local socks5 server - /// ( property is used for saving remote address/hostname for special use) - /// - public class Socks5LocalServer : Socks5Server - { - public Socks5LocalServer(string hostname, ushort port, string remoteHostname) - { - Hostname = hostname; - Port = port; - RemoteHostname = remoteHostname; - } +namespace Netch.Servers; - public string RemoteHostname { get; set; } +// TODO migrate to Socks5Server class +/// +/// Encrypted proxy client's local socks5 server +/// ( property is used for saving remote address/hostname for special use) +/// +public class Socks5LocalServer : Socks5Server +{ + public Socks5LocalServer(string hostname, ushort port, string remoteHostname) + { + Hostname = hostname; + Port = port; + RemoteHostname = remoteHostname; } + + public string RemoteHostname { get; set; } } \ No newline at end of file diff --git a/Netch/Servers/Socks5/Socks5Server.cs b/Netch/Servers/Socks5/Socks5Server.cs index 49197f9f..14161aff 100644 --- a/Netch/Servers/Socks5/Socks5Server.cs +++ b/Netch/Servers/Socks5/Socks5Server.cs @@ -1,45 +1,44 @@ using Netch.Models; -namespace Netch.Servers +namespace Netch.Servers; + +public class Socks5Server : Server { - public class Socks5Server : Server + /// + /// 密码 + /// + public string? Password { get; set; } + + /// + /// 账号 + /// + public string? Username { get; set; } + + public override string Type { get; } = "Socks5"; + + public override string MaskedData() { - /// - /// 密码 - /// - public string? Password { get; set; } + return $"Auth: {Auth()}"; + } - /// - /// 账号 - /// - public string? Username { get; set; } + public Socks5Server() + { + } - public override string Type { get; } = "Socks5"; + public Socks5Server(string hostname, ushort port) + { + Hostname = hostname; + Port = port; + } - public override string MaskedData() - { - return $"Auth: {Auth()}"; - } + public Socks5Server(string hostname, ushort port, string username, string password) : this(hostname, port) + { + Username = username; + Password = password; + } - public Socks5Server() - { - } - - public Socks5Server(string hostname, ushort port) - { - Hostname = hostname; - Port = port; - } - - public Socks5Server(string hostname, ushort port, string username, string password) : this(hostname, port) - { - Username = username; - Password = password; - } - - public bool Auth() - { - return !string.IsNullOrWhiteSpace(Username) && !string.IsNullOrWhiteSpace(Password); - } + public bool Auth() + { + return !string.IsNullOrWhiteSpace(Username) && !string.IsNullOrWhiteSpace(Password); } } \ No newline at end of file diff --git a/Netch/Servers/Socks5/Socks5Util.cs b/Netch/Servers/Socks5/Socks5Util.cs index 4e708a82..5675faf9 100644 --- a/Netch/Servers/Socks5/Socks5Util.cs +++ b/Netch/Servers/Socks5/Socks5Util.cs @@ -1,78 +1,74 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Netch.Interfaces; +using Netch.Interfaces; using Netch.Models; -namespace Netch.Servers +namespace Netch.Servers; + +public class Socks5Util : IServerUtil { - public class Socks5Util : IServerUtil + public ushort Priority { get; } = 0; + + public string TypeName { get; } = "Socks5"; + + public string FullName { get; } = "Socks5"; + + public string ShortName { get; } = "S5"; + + public string[] UriScheme { get; } = { }; + + public Type ServerType { get; } = typeof(Socks5Server); + + public void Edit(Server s) { - public ushort Priority { get; } = 0; + new Socks5Form((Socks5Server)s).ShowDialog(); + } - public string TypeName { get; } = "Socks5"; + public void Create() + { + new Socks5Form().ShowDialog(); + } - public string FullName { get; } = "Socks5"; + public string GetShareLink(Server s) + { + var server = (Socks5Server)s; + // https://t.me/socks?server=1.1.1.1&port=443 + return $"https://t.me/socks?server={server.Hostname}&port={server.Port}" + + $"{(!string.IsNullOrWhiteSpace(server.Username) ? $"&user={server.Username}" : "")}" + + $"{(server.Auth() ? $"&user={server.Password}" : "")}"; + } - public string ShortName { get; } = "S5"; + public IServerController GetController() + { + return new Socks5Controller(); + } - public string[] UriScheme { get; } = { }; + public IEnumerable ParseUri(string text) + { + var dict = text.Replace("tg://socks?", "") + .Replace("https://t.me/socks?", "") + .Split('&') + .Select(str => str.Split('=')) + .ToDictionary(splited => splited[0], splited => splited[1]); - public Type ServerType { get; } = typeof(Socks5Server); + if (!dict.ContainsKey("server") || !dict.ContainsKey("port")) + throw new FormatException(); - public void Edit(Server s) + var data = new Socks5Server { - new Socks5Form((Socks5Server)s).ShowDialog(); - } + Hostname = dict["server"], + Port = ushort.Parse(dict["port"]) + }; - public void Create() - { - new Socks5Form().ShowDialog(); - } + if (dict.ContainsKey("user") && !string.IsNullOrWhiteSpace(dict["user"])) + data.Username = dict["user"]; - public string GetShareLink(Server s) - { - var server = (Socks5Server)s; - // https://t.me/socks?server=1.1.1.1&port=443 - return $"https://t.me/socks?server={server.Hostname}&port={server.Port}" + - $"{(!string.IsNullOrWhiteSpace(server.Username) ? $"&user={server.Username}" : "")}" + - $"{(server.Auth() ? $"&user={server.Password}" : "")}"; - } + if (dict.ContainsKey("pass") && !string.IsNullOrWhiteSpace(dict["pass"])) + data.Password = dict["pass"]; - public IServerController GetController() - { - return new Socks5Controller(); - } + return new[] { data }; + } - public IEnumerable ParseUri(string text) - { - var dict = text.Replace("tg://socks?", "") - .Replace("https://t.me/socks?", "") - .Split('&') - .Select(str => str.Split('=')) - .ToDictionary(splited => splited[0], splited => splited[1]); - - if (!dict.ContainsKey("server") || !dict.ContainsKey("port")) - throw new FormatException(); - - var data = new Socks5Server - { - Hostname = dict["server"], - Port = ushort.Parse(dict["port"]) - }; - - if (dict.ContainsKey("user") && !string.IsNullOrWhiteSpace(dict["user"])) - data.Username = dict["user"]; - - if (dict.ContainsKey("pass") && !string.IsNullOrWhiteSpace(dict["pass"])) - data.Password = dict["pass"]; - - return new[] { data }; - } - - public bool CheckServer(Server s) - { - return true; - } + public bool CheckServer(Server s) + { + return true; } } \ No newline at end of file diff --git a/Netch/Servers/Trojan/TrojanConfig.cs b/Netch/Servers/Trojan/TrojanConfig.cs index 056506b5..315bd6c7 100644 --- a/Netch/Servers/Trojan/TrojanConfig.cs +++ b/Netch/Servers/Trojan/TrojanConfig.cs @@ -1,88 +1,85 @@ #nullable disable -using System.Collections.Generic; +namespace Netch.Servers; -namespace Netch.Servers +public class TrojanConfig { - public class TrojanConfig + /// + /// 监听地址 + /// + public string local_addr { get; set; } = "127.0.0.1"; + + /// + /// 监听端口 + /// + public int local_port { get; set; } = 2801; + + /// + /// 日志级别 + /// + public int log_level { get; set; } = 1; + + /// + /// 密码 + /// + public List password { get; set; } + + /// + /// 远端地址 + /// + public string remote_addr { get; set; } + + /// + /// 远端端口 + /// + public int remote_port { get; set; } + + /// + /// 启动类型 + /// + public string run_type { get; set; } = "client"; + + public TrojanSSL ssl { get; set; } = new(); + + public TrojanTCP tcp { get; set; } = new(); +} + +public class TrojanSSL +{ + public List alpn { get; set; } = new() { - /// - /// 监听地址 - /// - public string local_addr { get; set; } = "127.0.0.1"; + "h2", + "http/1.1" + }; - /// - /// 监听端口 - /// - public int local_port { get; set; } = 2801; + public string cert { get; set; } - /// - /// 日志级别 - /// - public int log_level { get; set; } = 1; + public string cipher { get; set; } = + "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA"; - /// - /// 密码 - /// - public List password { get; set; } + public string cipher_tls13 { get; set; } = "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384"; - /// - /// 远端地址 - /// - public string remote_addr { get; set; } + public string curves { get; set; } = string.Empty; - /// - /// 远端端口 - /// - public int remote_port { get; set; } + public bool reuse_session { get; set; } = true; - /// - /// 启动类型 - /// - public string run_type { get; set; } = "client"; + public bool session_ticket { get; set; } = true; - public TrojanSSL ssl { get; set; } = new(); + public string sni { get; set; } = string.Empty; - public TrojanTCP tcp { get; set; } = new(); - } + public bool verify { get; set; } = false; - public class TrojanSSL - { - public List alpn { get; set; } = new() - { - "h2", - "http/1.1" - }; + public bool verify_hostname { get; set; } = false; +} - public string cert { get; set; } +public class TrojanTCP +{ + public bool fast_open { get; set; } = true; - public string cipher { get; set; } = - "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA"; + public int fast_open_qlen { get; set; } = 20; - public string cipher_tls13 { get; set; } = "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384"; + public bool keep_alive { get; set; } = true; - public string curves { get; set; } = string.Empty; + public bool no_delay { get; set; } = false; - public bool reuse_session { get; set; } = true; - - public bool session_ticket { get; set; } = true; - - public string sni { get; set; } = string.Empty; - - public bool verify { get; set; } = false; - - public bool verify_hostname { get; set; } = false; - } - - public class TrojanTCP - { - public bool fast_open { get; set; } = true; - - public int fast_open_qlen { get; set; } = 20; - - public bool keep_alive { get; set; } = true; - - public bool no_delay { get; set; } = false; - - public bool reuse_port { get; set; } = false; - } + public bool reuse_port { get; set; } = false; } \ No newline at end of file diff --git a/Netch/Servers/Trojan/TrojanController.cs b/Netch/Servers/Trojan/TrojanController.cs index fcb31942..398eac49 100644 --- a/Netch/Servers/Trojan/TrojanController.cs +++ b/Netch/Servers/Trojan/TrojanController.cs @@ -1,57 +1,53 @@ -using System.Collections.Generic; -using System.IO; -using System.Net; +using System.Net; using System.Text.Json; -using System.Threading.Tasks; using Netch.Controllers; using Netch.Interfaces; using Netch.Models; using Netch.Utils; -namespace Netch.Servers +namespace Netch.Servers; + +public class TrojanController : Guard, IServerController { - public class TrojanController : Guard, IServerController + public TrojanController() : base("Trojan.exe") { - public TrojanController() : base("Trojan.exe") + } + + protected override IEnumerable StartedKeywords => new[] { "started" }; + + protected override IEnumerable FailedKeywords => new[] { "exiting" }; + + public override string Name => "Trojan"; + + public ushort? Socks5LocalPort { get; set; } + + public string? LocalAddress { get; set; } + + public async Task StartAsync(Server s) + { + var server = (TrojanServer)s; + var trojanConfig = new TrojanConfig { - } - - protected override IEnumerable StartedKeywords => new[] { "started" }; - - protected override IEnumerable FailedKeywords => new[] { "exiting" }; - - public override string Name => "Trojan"; - - public ushort? Socks5LocalPort { get; set; } - - public string? LocalAddress { get; set; } - - public async Task StartAsync(Server s) - { - var server = (TrojanServer)s; - var trojanConfig = new TrojanConfig + local_addr = this.LocalAddress(), + local_port = this.Socks5LocalPort(), + remote_addr = await server.AutoResolveHostnameAsync(), + remote_port = server.Port, + password = new List { - local_addr = this.LocalAddress(), - local_port = this.Socks5LocalPort(), - remote_addr = await server.AutoResolveHostnameAsync(), - remote_port = server.Port, - password = new List - { - server.Password - }, - ssl = new TrojanSSL - { - sni = server.Host.ValueOrDefault() ?? server.Hostname - } - }; - - await using (var fileStream = new FileStream(Constants.TempConfig, FileMode.Create, FileAccess.Write, FileShare.Read)) + server.Password + }, + ssl = new TrojanSSL { - await JsonSerializer.SerializeAsync(fileStream, trojanConfig, Global.NewCustomJsonSerializerOptions()); + sni = server.Host.ValueOrDefault() ?? server.Hostname } + }; - await StartGuardAsync("-c ..\\data\\last.json"); - return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname); + await using (var fileStream = new FileStream(Constants.TempConfig, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + await JsonSerializer.SerializeAsync(fileStream, trojanConfig, Global.NewCustomJsonSerializerOptions()); } + + await StartGuardAsync("-c ..\\data\\last.json"); + return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), server.Hostname); } } \ No newline at end of file diff --git a/Netch/Servers/Trojan/TrojanForm.cs b/Netch/Servers/Trojan/TrojanForm.cs index 057463b4..8d007476 100644 --- a/Netch/Servers/Trojan/TrojanForm.cs +++ b/Netch/Servers/Trojan/TrojanForm.cs @@ -1,17 +1,16 @@ using Netch.Forms; -namespace Netch.Servers -{ - public class TrojanForm : ServerForm - { - public TrojanForm(TrojanServer? server = default) - { - server ??= new TrojanServer(); - Server = server; - CreateTextBox("Password", "Password", s => true, s => server.Password = s, server.Password); - CreateTextBox("Host", "Host", s => true, s => server.Host = s, server.Host); - } +namespace Netch.Servers; - protected override string TypeName { get; } = "Trojan"; +public class TrojanForm : ServerForm +{ + public TrojanForm(TrojanServer? server = default) + { + server ??= new TrojanServer(); + Server = server; + CreateTextBox("Password", "Password", s => true, s => server.Password = s, server.Password); + CreateTextBox("Host", "Host", s => true, s => server.Host = s, server.Host); } + + protected override string TypeName { get; } = "Trojan"; } \ No newline at end of file diff --git a/Netch/Servers/Trojan/TrojanServer.cs b/Netch/Servers/Trojan/TrojanServer.cs index bf7beee6..2a2bc32f 100644 --- a/Netch/Servers/Trojan/TrojanServer.cs +++ b/Netch/Servers/Trojan/TrojanServer.cs @@ -1,23 +1,23 @@ using Netch.Models; -namespace Netch.Servers +namespace Netch.Servers; + +public class TrojanServer : Server { - public class TrojanServer : Server + public override string Type { get; } = "Trojan"; + + public override string MaskedData() { - public override string Type { get; } = "Trojan"; - public override string MaskedData() - { - return ""; - } - - /// - /// 密码 - /// - public string Password { get; set; } = string.Empty; - - /// - /// 伪装域名 - /// - public string? Host { get; set; } + return ""; } + + /// + /// 密码 + /// + public string Password { get; set; } = string.Empty; + + /// + /// 伪装域名 + /// + public string? Host { get; set; } } \ No newline at end of file diff --git a/Netch/Servers/Trojan/TrojanUtil.cs b/Netch/Servers/Trojan/TrojanUtil.cs index 3ba46e2a..09e1f666 100644 --- a/Netch/Servers/Trojan/TrojanUtil.cs +++ b/Netch/Servers/Trojan/TrojanUtil.cs @@ -1,89 +1,86 @@ -using System; -using System.Collections.Generic; using System.Text.RegularExpressions; using System.Web; using Netch.Interfaces; using Netch.Models; -namespace Netch.Servers +namespace Netch.Servers; + +public class TrojanUtil : IServerUtil { - public class TrojanUtil : IServerUtil + public ushort Priority { get; } = 3; + + public string TypeName { get; } = "Trojan"; + + public string FullName { get; } = "Trojan"; + + public string ShortName { get; } = "TR"; + + public string[] UriScheme { get; } = { "trojan" }; + + public Type ServerType { get; } = typeof(TrojanServer); + + public void Edit(Server s) { - public ushort Priority { get; } = 3; + new TrojanForm((TrojanServer)s).ShowDialog(); + } - public string TypeName { get; } = "Trojan"; + public void Create() + { + new TrojanForm().ShowDialog(); + } - public string FullName { get; } = "Trojan"; + public string GetShareLink(Server s) + { + var server = (TrojanServer)s; + return $"trojan://{HttpUtility.UrlEncode(server.Password)}@{server.Hostname}:{server.Port}#{server.Remark}"; + } - public string ShortName { get; } = "TR"; + public IServerController GetController() + { + return new TrojanController(); + } - public string[] UriScheme { get; } = { "trojan" }; + public IEnumerable ParseUri(string text) + { + var data = new TrojanServer(); - public Type ServerType { get; } = typeof(TrojanServer); - - public void Edit(Server s) + text = text.Replace("/?", "?"); + if (text.Contains("#")) { - new TrojanForm((TrojanServer)s).ShowDialog(); + data.Remark = HttpUtility.UrlDecode(text.Split('#')[1]); + text = text.Split('#')[0]; } - public void Create() + if (text.Contains("?")) { - new TrojanForm().ShowDialog(); - } + var reg = new Regex(@"^(?.+?)\?(.+)$"); + var regmatch = reg.Match(text); - public string GetShareLink(Server s) - { - var server = (TrojanServer)s; - return $"trojan://{HttpUtility.UrlEncode(server.Password)}@{server.Hostname}:{server.Port}#{server.Remark}"; - } - - public IServerController GetController() - { - return new TrojanController(); - } - - public IEnumerable ParseUri(string text) - { - var data = new TrojanServer(); - - text = text.Replace("/?", "?"); - if (text.Contains("#")) - { - data.Remark = HttpUtility.UrlDecode(text.Split('#')[1]); - text = text.Split('#')[0]; - } - - if (text.Contains("?")) - { - var reg = new Regex(@"^(?.+?)\?(.+)$"); - var regmatch = reg.Match(text); - - if (!regmatch.Success) - throw new FormatException(); - - var peer = HttpUtility.UrlDecode(HttpUtility.ParseQueryString(new Uri(text).Query).Get("peer")); - - if (peer != null) - data.Host = peer; - - text = regmatch.Groups["data"].Value; - } - - var finder = new Regex(@"^trojan://(?.+?)@(?.+):(?\d+)"); - var match = finder.Match(text); - if (!match.Success) + if (!regmatch.Success) throw new FormatException(); - data.Password = HttpUtility.UrlDecode(match.Groups["psk"].Value); - data.Hostname = match.Groups["server"].Value; - data.Port = ushort.Parse(match.Groups["port"].Value); + var peer = HttpUtility.UrlDecode(HttpUtility.ParseQueryString(new Uri(text).Query).Get("peer")); - return new[] { data }; + if (peer != null) + data.Host = peer; + + text = regmatch.Groups["data"].Value; } - public bool CheckServer(Server s) - { - return true; - } + var finder = new Regex(@"^trojan://(?.+?)@(?.+):(?\d+)"); + var match = finder.Match(text); + if (!match.Success) + throw new FormatException(); + + data.Password = HttpUtility.UrlDecode(match.Groups["psk"].Value); + data.Hostname = match.Groups["server"].Value; + data.Port = ushort.Parse(match.Groups["port"].Value); + + return new[] { data }; + } + + public bool CheckServer(Server s) + { + return true; } } \ No newline at end of file diff --git a/Netch/Servers/V2ray/ShareModels/V2rayNJObject.cs b/Netch/Servers/V2ray/ShareModels/V2rayNJObject.cs index 14020dc1..deea2947 100644 --- a/Netch/Servers/V2ray/ShareModels/V2rayNJObject.cs +++ b/Netch/Servers/V2ray/ShareModels/V2rayNJObject.cs @@ -1,70 +1,69 @@ -namespace Netch.Servers +namespace Netch.Servers; + +public class V2rayNJObject { - public class V2rayNJObject - { - /// - /// 链接版本 - /// - public int v { get; set; } = 2; + /// + /// 链接版本 + /// + public int v { get; set; } = 2; - /// - /// 备注 - /// - public string ps { get; set; } = string.Empty; + /// + /// 备注 + /// + public string ps { get; set; } = string.Empty; - /// - /// 地址 - /// - public string add { get; set; } = string.Empty; + /// + /// 地址 + /// + public string add { get; set; } = string.Empty; - /// - /// 端口 - /// - public ushort port { get; set; } + /// + /// 端口 + /// + public ushort port { get; set; } - /// - /// 用户 ID - /// - public string id { get; set; } = string.Empty; + /// + /// 用户 ID + /// + public string id { get; set; } = string.Empty; - /// - /// 额外 ID - /// - public int aid { get; set; } + /// + /// 额外 ID + /// + public int aid { get; set; } - /// - /// 加密方式 (security) - /// - public string scy { get; set; } = "auto"; + /// + /// 加密方式 (security) + /// + public string scy { get; set; } = "auto"; - /// - /// 传输协议 - /// - public string net { get; set; } = string.Empty; + /// + /// 传输协议 + /// + public string net { get; set; } = string.Empty; - /// - /// 伪装类型 - /// - public string type { get; set; } = string.Empty; + /// + /// 伪装类型 + /// + public string type { get; set; } = string.Empty; - /// - /// 伪装域名(HTTP,WS) - /// - public string host { get; set; } = string.Empty; + /// + /// 伪装域名(HTTP,WS) + /// + public string host { get; set; } = string.Empty; - /// - /// 伪装路径/服务名称 - /// - public string path { get; set; } = string.Empty; + /// + /// 伪装路径/服务名称 + /// + public string path { get; set; } = string.Empty; - /// - /// 是否使用 TLS - /// - public string tls { get; set; } = string.Empty; + /// + /// 是否使用 TLS + /// + public string tls { get; set; } = string.Empty; - /// - /// serverName - /// - public string sni { get; set; } = string.Empty; - } + /// + /// serverName + /// + public string sni { get; set; } = string.Empty; } \ No newline at end of file diff --git a/Netch/Servers/V2ray/V2rayConfig.cs b/Netch/Servers/V2ray/V2rayConfig.cs index fa277fa2..349c1430 100644 --- a/Netch/Servers/V2ray/V2rayConfig.cs +++ b/Netch/Servers/V2ray/V2rayConfig.cs @@ -1,149 +1,148 @@ #nullable disable // ReSharper disable InconsistentNaming -namespace Netch.Servers +namespace Netch.Servers; + +public struct V2rayConfig { - public struct V2rayConfig - { - public object[] inbounds { get; set; } + public object[] inbounds { get; set; } - public Outbound[] outbounds { get; set; } - } + public Outbound[] outbounds { get; set; } +} - public class User - { - public string id { get; set; } +public class User +{ + public string id { get; set; } - public int alterId { get; set; } + public int alterId { get; set; } - public string security { get; set; } + public string security { get; set; } - public string encryption { get; set; } + public string encryption { get; set; } - public string flow { get; set; } - } + public string flow { get; set; } +} - public class Outbound - { - public string protocol { get; set; } +public class Outbound +{ + public string protocol { get; set; } - public OutboundConfiguration settings { get; set; } + public OutboundConfiguration settings { get; set; } - public StreamSettings streamSettings { get; set; } + public StreamSettings streamSettings { get; set; } - public Mux mux { get; set; } - } + public Mux mux { get; set; } +} - public class OutboundConfiguration - { - public VnextItem[] vnext { get; set; } +public class OutboundConfiguration +{ + public VnextItem[] vnext { get; set; } - public object[] servers { get; set; } - } + public object[] servers { get; set; } +} - public class VnextItem - { - public string address { get; set; } +public class VnextItem +{ + public string address { get; set; } - public ushort port { get; set; } + public ushort port { get; set; } - public User[] users { get; set; } - } + public User[] users { get; set; } +} - public class Mux - { - public bool enabled { get; set; } +public class Mux +{ + public bool enabled { get; set; } - public int concurrency { get; set; } - } + public int concurrency { get; set; } +} - public class StreamSettings - { - public string network { get; set; } +public class StreamSettings +{ + public string network { get; set; } - public string security { get; set; } + public string security { get; set; } - public TlsSettings tlsSettings { get; set; } + public TlsSettings tlsSettings { get; set; } - public TcpSettings tcpSettings { get; set; } + public TcpSettings tcpSettings { get; set; } - public KcpSettings kcpSettings { get; set; } + public KcpSettings kcpSettings { get; set; } - public WsSettings wsSettings { get; set; } + public WsSettings wsSettings { get; set; } - public HttpSettings httpSettings { get; set; } + public HttpSettings httpSettings { get; set; } - public QuicSettings quicSettings { get; set; } + public QuicSettings quicSettings { get; set; } - public TlsSettings xtlsSettings { get; set; } + public TlsSettings xtlsSettings { get; set; } - public GrpcSettings grpcSettings { get; set; } - } + public GrpcSettings grpcSettings { get; set; } +} - #region Transport +#region Transport - public class TlsSettings - { - public bool allowInsecure { get; set; } +public class TlsSettings +{ + public bool allowInsecure { get; set; } - public string serverName { get; set; } - } + public string serverName { get; set; } +} - public class TcpSettings - { - public object header { get; set; } - } +public class TcpSettings +{ + public object header { get; set; } +} - public class WsSettings - { - public string path { get; set; } +public class WsSettings +{ + public string path { get; set; } - public object headers { get; set; } - } + public object headers { get; set; } +} - public class KcpSettings - { - public int mtu { get; set; } +public class KcpSettings +{ + public int mtu { get; set; } - public int tti { get; set; } + public int tti { get; set; } - public int uplinkCapacity { get; set; } + public int uplinkCapacity { get; set; } - public int downlinkCapacity { get; set; } + public int downlinkCapacity { get; set; } - public bool congestion { get; set; } + public bool congestion { get; set; } - public int readBufferSize { get; set; } + public int readBufferSize { get; set; } - public int writeBufferSize { get; set; } + public int writeBufferSize { get; set; } - public object header { get; set; } + public object header { get; set; } - public string seed { get; set; } - } + public string seed { get; set; } +} - public class HttpSettings - { - public string path { get; set; } +public class HttpSettings +{ + public string path { get; set; } - public string[] host { get; set; } - } + public string[] host { get; set; } +} - public class QuicSettings - { - public string security { get; set; } +public class QuicSettings +{ + public string security { get; set; } - public string key { get; set; } + public string key { get; set; } - public object header { get; set; } - } + public object header { get; set; } +} - public class GrpcSettings - { - public string serviceName { get; set; } +public class GrpcSettings +{ + public string serviceName { get; set; } - public bool multiMode { get; set; } - } + public bool multiMode { get; set; } +} - #endregion -} \ No newline at end of file +#endregion \ No newline at end of file diff --git a/Netch/Servers/V2ray/V2rayConfigUtils.cs b/Netch/Servers/V2ray/V2rayConfigUtils.cs index 96f4b114..2e54838a 100644 --- a/Netch/Servers/V2ray/V2rayConfigUtils.cs +++ b/Netch/Servers/V2ray/V2rayConfigUtils.cs @@ -1,264 +1,262 @@ -using System.Threading.Tasks; -using Netch.Models; +using Netch.Models; using Netch.Utils; #pragma warning disable VSTHRD200 -namespace Netch.Servers +namespace Netch.Servers; + +public static class V2rayConfigUtils { - public static class V2rayConfigUtils + public static async Task GenerateClientConfigAsync(Server server) { - public static async Task GenerateClientConfigAsync(Server server) + var v2rayConfig = new V2rayConfig { - var v2rayConfig = new V2rayConfig + inbounds = new object[] { - inbounds = new object[] + new + { + port = Global.Settings.Socks5LocalPort, + protocol = "socks", + listen = Global.Settings.LocalAddress, + settings = new + { + udp = true + } + } + } + }; + + v2rayConfig.outbounds = new[] { await outbound(server) }; + + return v2rayConfig; + } + + private static async Task outbound(Server server) + { + var outbound = new Outbound + { + settings = new OutboundConfiguration(), + mux = new Mux() + }; + + switch (server) + { + case Socks5Server socks5: + { + outbound.protocol = "socks"; + outbound.settings.servers = new object[] { new { - port = Global.Settings.Socks5LocalPort, - protocol = "socks", - listen = Global.Settings.LocalAddress, - settings = new - { - udp = true - } - } - } - }; - - v2rayConfig.outbounds = new[] { await outbound(server) }; - - return v2rayConfig; - } - - private static async Task outbound(Server server) - { - var outbound = new Outbound - { - settings = new OutboundConfiguration(), - mux = new Mux() - }; - - switch (server) - { - case Socks5Server socks5: - { - outbound.protocol = "socks"; - outbound.settings.servers = new object[] - { - new - { - address = await server.AutoResolveHostnameAsync(), - port = server.Port, - users = socks5.Auth() - ? new[] - { - new - { - user = socks5.Username, - pass = socks5.Password, - level = 1 - } - } - : null - } - }; - - outbound.mux.enabled = false; - outbound.mux.concurrency = -1; - break; - } - case VLESSServer vless: - { - outbound.protocol = "vless"; - outbound.settings.vnext = new[] - { - new VnextItem - { - address = await server.AutoResolveHostnameAsync(), - port = server.Port, - users = new[] + address = await server.AutoResolveHostnameAsync(), + port = server.Port, + users = socks5.Auth() + ? new[] { - new User + new { - id = vless.UserID, - flow = vless.Flow.ValueOrDefault(), - encryption = vless.EncryptMethod + user = socks5.Username, + pass = socks5.Password, + level = 1 } } - } - }; - - outbound.streamSettings = boundStreamSettings(vless); - - if (vless.TLSSecureType == "xtls") - { - outbound.mux.enabled = false; - outbound.mux.concurrency = -1; + : null } - else - { - outbound.mux.enabled = vless.UseMux ?? Global.Settings.V2RayConfig.UseMux; - outbound.mux.concurrency = vless.UseMux ?? Global.Settings.V2RayConfig.UseMux ? 8 : -1; - } - - break; - } - case VMessServer vmess: - { - outbound.protocol = "vmess"; - outbound.settings.vnext = new[] - { - new VnextItem - { - address = await server.AutoResolveHostnameAsync(), - port = server.Port, - users = new[] - { - new User - { - id = vmess.UserID, - alterId = vmess.AlterID, - security = vmess.EncryptMethod - } - } - } - }; - - outbound.streamSettings = boundStreamSettings(vmess); - - outbound.mux.enabled = vmess.UseMux ?? Global.Settings.V2RayConfig.UseMux; - outbound.mux.concurrency = vmess.UseMux ?? Global.Settings.V2RayConfig.UseMux ? 8 : -1; - break; - } - } - - return outbound; - } - - private static StreamSettings boundStreamSettings(VMessServer server) - { - // https://xtls.github.io/config/transports - - var streamSettings = new StreamSettings - { - network = server.TransferProtocol, - security = server.TLSSecureType - }; - - if (server.TLSSecureType != "none") - { - var tlsSettings = new TlsSettings - { - allowInsecure = Global.Settings.V2RayConfig.AllowInsecure, - serverName = server.ServerName.ValueOrDefault() ?? server.Host.SplitOrDefault()?[0] }; - switch (server.TLSSecureType) - { - case "tls": - streamSettings.tlsSettings = tlsSettings; - break; - case "xtls": - streamSettings.xtlsSettings = tlsSettings; - break; - } + outbound.mux.enabled = false; + outbound.mux.concurrency = -1; + break; } - - switch (server.TransferProtocol) + case VLESSServer vless: { - case "tcp": - - streamSettings.tcpSettings = new TcpSettings + outbound.protocol = "vless"; + outbound.settings.vnext = new[] + { + new VnextItem { - header = new + address = await server.AutoResolveHostnameAsync(), + port = server.Port, + users = new[] { - type = server.FakeType, - request = server.FakeType switch + new User { - "none" => null, - "http" => new - { - path = server.Path.SplitOrDefault(), - headers = new - { - Host = server.Host.SplitOrDefault() - } - }, - _ => throw new MessageException($"Invalid tcp type {server.FakeType}") + id = vless.UserID, + flow = vless.Flow.ValueOrDefault(), + encryption = vless.EncryptMethod } } - }; + } + }; - break; - case "ws": + outbound.streamSettings = boundStreamSettings(vless); - streamSettings.wsSettings = new WsSettings - { - path = server.Path.ValueOrDefault(), - headers = new - { - Host = server.Host.ValueOrDefault() - } - }; + if (vless.TLSSecureType == "xtls") + { + outbound.mux.enabled = false; + outbound.mux.concurrency = -1; + } + else + { + outbound.mux.enabled = vless.UseMux ?? Global.Settings.V2RayConfig.UseMux; + outbound.mux.concurrency = vless.UseMux ?? Global.Settings.V2RayConfig.UseMux ? 8 : -1; + } - break; - case "kcp": - - streamSettings.kcpSettings = new KcpSettings - { - mtu = Global.Settings.V2RayConfig.KcpConfig.mtu, - tti = Global.Settings.V2RayConfig.KcpConfig.tti, - uplinkCapacity = Global.Settings.V2RayConfig.KcpConfig.uplinkCapacity, - downlinkCapacity = Global.Settings.V2RayConfig.KcpConfig.downlinkCapacity, - congestion = Global.Settings.V2RayConfig.KcpConfig.congestion, - readBufferSize = Global.Settings.V2RayConfig.KcpConfig.readBufferSize, - writeBufferSize = Global.Settings.V2RayConfig.KcpConfig.writeBufferSize, - header = new - { - type = server.FakeType - }, - seed = server.Path.ValueOrDefault() - }; - - break; - case "h2": - - streamSettings.httpSettings = new HttpSettings - { - host = server.Host.SplitOrDefault(), - path = server.Path.ValueOrDefault() - }; - - break; - case "quic": - - streamSettings.quicSettings = new QuicSettings - { - security = server.QUICSecure, - key = server.QUICSecret, - header = new - { - type = server.FakeType - } - }; - - break; - case "grpc": - - streamSettings.grpcSettings = new GrpcSettings - { - serviceName = server.Path, - multiMode = server.FakeType == "multi" - }; - - break; - default: - throw new MessageException($"transfer protocol \"{server.TransferProtocol}\" not implemented yet"); + break; } + case VMessServer vmess: + { + outbound.protocol = "vmess"; + outbound.settings.vnext = new[] + { + new VnextItem + { + address = await server.AutoResolveHostnameAsync(), + port = server.Port, + users = new[] + { + new User + { + id = vmess.UserID, + alterId = vmess.AlterID, + security = vmess.EncryptMethod + } + } + } + }; - return streamSettings; + outbound.streamSettings = boundStreamSettings(vmess); + + outbound.mux.enabled = vmess.UseMux ?? Global.Settings.V2RayConfig.UseMux; + outbound.mux.concurrency = vmess.UseMux ?? Global.Settings.V2RayConfig.UseMux ? 8 : -1; + break; + } } + + return outbound; + } + + private static StreamSettings boundStreamSettings(VMessServer server) + { + // https://xtls.github.io/config/transports + + var streamSettings = new StreamSettings + { + network = server.TransferProtocol, + security = server.TLSSecureType + }; + + if (server.TLSSecureType != "none") + { + var tlsSettings = new TlsSettings + { + allowInsecure = Global.Settings.V2RayConfig.AllowInsecure, + serverName = server.ServerName.ValueOrDefault() ?? server.Host.SplitOrDefault()?[0] + }; + + switch (server.TLSSecureType) + { + case "tls": + streamSettings.tlsSettings = tlsSettings; + break; + case "xtls": + streamSettings.xtlsSettings = tlsSettings; + break; + } + } + + switch (server.TransferProtocol) + { + case "tcp": + + streamSettings.tcpSettings = new TcpSettings + { + header = new + { + type = server.FakeType, + request = server.FakeType switch + { + "none" => null, + "http" => new + { + path = server.Path.SplitOrDefault(), + headers = new + { + Host = server.Host.SplitOrDefault() + } + }, + _ => throw new MessageException($"Invalid tcp type {server.FakeType}") + } + } + }; + + break; + case "ws": + + streamSettings.wsSettings = new WsSettings + { + path = server.Path.ValueOrDefault(), + headers = new + { + Host = server.Host.ValueOrDefault() + } + }; + + break; + case "kcp": + + streamSettings.kcpSettings = new KcpSettings + { + mtu = Global.Settings.V2RayConfig.KcpConfig.mtu, + tti = Global.Settings.V2RayConfig.KcpConfig.tti, + uplinkCapacity = Global.Settings.V2RayConfig.KcpConfig.uplinkCapacity, + downlinkCapacity = Global.Settings.V2RayConfig.KcpConfig.downlinkCapacity, + congestion = Global.Settings.V2RayConfig.KcpConfig.congestion, + readBufferSize = Global.Settings.V2RayConfig.KcpConfig.readBufferSize, + writeBufferSize = Global.Settings.V2RayConfig.KcpConfig.writeBufferSize, + header = new + { + type = server.FakeType + }, + seed = server.Path.ValueOrDefault() + }; + + break; + case "h2": + + streamSettings.httpSettings = new HttpSettings + { + host = server.Host.SplitOrDefault(), + path = server.Path.ValueOrDefault() + }; + + break; + case "quic": + + streamSettings.quicSettings = new QuicSettings + { + security = server.QUICSecure, + key = server.QUICSecret, + header = new + { + type = server.FakeType + } + }; + + break; + case "grpc": + + streamSettings.grpcSettings = new GrpcSettings + { + serviceName = server.Path, + multiMode = server.FakeType == "multi" + }; + + break; + default: + throw new MessageException($"transfer protocol \"{server.TransferProtocol}\" not implemented yet"); + } + + return streamSettings; } } \ No newline at end of file diff --git a/Netch/Servers/V2ray/V2rayController.cs b/Netch/Servers/V2ray/V2rayController.cs index 74369b0f..0506f923 100644 --- a/Netch/Servers/V2ray/V2rayController.cs +++ b/Netch/Servers/V2ray/V2rayController.cs @@ -1,41 +1,37 @@ -using System.Collections.Generic; -using System.IO; using System.Net; using System.Text.Json; -using System.Threading.Tasks; using Netch.Controllers; using Netch.Interfaces; using Netch.Models; -namespace Netch.Servers +namespace Netch.Servers; + +public class V2rayController : Guard, IServerController { - public class V2rayController : Guard, IServerController + public V2rayController() : base("xray.exe") { - public V2rayController() : base("xray.exe") + if (!Global.Settings.V2RayConfig.XrayCone) + Instance.StartInfo.Environment["XRAY_CONE_DISABLED"] = "true"; + } + + protected override IEnumerable StartedKeywords => new[] { "started" }; + + protected override IEnumerable FailedKeywords => new[] { "config file not readable", "failed to" }; + + public override string Name => "Xray"; + + public ushort? Socks5LocalPort { get; set; } + + public string? LocalAddress { get; set; } + + public virtual async Task StartAsync(Server s) + { + await using (var fileStream = new FileStream(Constants.TempConfig, FileMode.Create, FileAccess.Write, FileShare.Read)) { - if (!Global.Settings.V2RayConfig.XrayCone) - Instance.StartInfo.Environment["XRAY_CONE_DISABLED"] = "true"; + await JsonSerializer.SerializeAsync(fileStream, await V2rayConfigUtils.GenerateClientConfigAsync(s), Global.NewCustomJsonSerializerOptions()); } - protected override IEnumerable StartedKeywords => new[] { "started" }; - - protected override IEnumerable FailedKeywords => new[] { "config file not readable", "failed to" }; - - public override string Name => "Xray"; - - public ushort? Socks5LocalPort { get; set; } - - public string? LocalAddress { get; set; } - - public virtual async Task StartAsync(Server s) - { - await using (var fileStream = new FileStream(Constants.TempConfig, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - await JsonSerializer.SerializeAsync(fileStream, await V2rayConfigUtils.GenerateClientConfigAsync(s), Global.NewCustomJsonSerializerOptions()); - } - - await StartGuardAsync("-config ..\\data\\last.json"); - return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), s.Hostname); - } + await StartGuardAsync("-config ..\\data\\last.json"); + return new Socks5LocalServer(IPAddress.Loopback.ToString(), this.Socks5LocalPort(), s.Hostname); } } \ No newline at end of file diff --git a/Netch/Servers/V2ray/V2rayUtils.cs b/Netch/Servers/V2ray/V2rayUtils.cs index f11b90ed..8ff2f272 100644 --- a/Netch/Servers/V2ray/V2rayUtils.cs +++ b/Netch/Servers/V2ray/V2rayUtils.cs @@ -1,151 +1,147 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; using System.Web; using Netch.Models; using Netch.Utils; -namespace Netch.Servers +namespace Netch.Servers; + +public static class V2rayUtils { - public static class V2rayUtils + public static IEnumerable ParseVUri(string text) { - public static IEnumerable ParseVUri(string text) + var scheme = ShareLink.GetUriScheme(text).ToLower(); + var server = scheme switch { "vmess" => new VMessServer(), "vless" => new VLESSServer(), _ => throw new ArgumentOutOfRangeException() }; + if (text.Contains("#")) { - var scheme = ShareLink.GetUriScheme(text).ToLower(); - var server = scheme switch { "vmess" => new VMessServer(), "vless" => new VLESSServer(), _ => throw new ArgumentOutOfRangeException() }; - if (text.Contains("#")) - { - server.Remark = Uri.UnescapeDataString(text.Split('#')[1]); - text = text.Split('#')[0]; - } - - if (text.Contains("?")) - { - var parameter = HttpUtility.ParseQueryString(text.Split('?')[1]); - text = text.Substring(0, text.IndexOf("?", StringComparison.Ordinal)); - server.TransferProtocol = parameter.Get("type") ?? "tcp"; - server.EncryptMethod = parameter.Get("encryption") ?? scheme switch { "vless" => "none", _ => "auto" }; - switch (server.TransferProtocol) - { - case "tcp": - break; - case "kcp": - server.FakeType = parameter.Get("headerType") ?? "none"; - server.Path = Uri.UnescapeDataString(parameter.Get("seed") ?? ""); - break; - case "ws": - server.Path = Uri.UnescapeDataString(parameter.Get("path") ?? "/"); - server.Host = Uri.UnescapeDataString(parameter.Get("host") ?? ""); - break; - case "h2": - server.Path = Uri.UnescapeDataString(parameter.Get("path") ?? "/"); - server.Host = Uri.UnescapeDataString(parameter.Get("host") ?? ""); - break; - case "quic": - server.QUICSecure = parameter.Get("quicSecurity") ?? "none"; - server.QUICSecret = parameter.Get("key") ?? ""; - server.FakeType = parameter.Get("headerType") ?? "none"; - break; - case "grpc": - server.FakeType = parameter.Get("mode") ?? "gun"; - server.Path = parameter.Get("serviceName") ?? ""; - break; - } - - server.TLSSecureType = parameter.Get("security") ?? "none"; - if (server.TLSSecureType != "none") - { - server.ServerName = parameter.Get("sni") ?? ""; - if (server.TLSSecureType == "xtls") - ((VLESSServer)server).Flow = parameter.Get("flow") ?? ""; - } - } - - var finder = new Regex(@$"^{scheme}://(?.+?)@(?.+):(?\d+)"); - var match = finder.Match(text.Split('?')[0]); - if (!match.Success) - throw new FormatException(); - - server.UserID = match.Groups["guid"].Value; - server.Hostname = match.Groups["server"].Value; - server.Port = ushort.Parse(match.Groups["port"].Value); - - return new[] { server }; + server.Remark = Uri.UnescapeDataString(text.Split('#')[1]); + text = text.Split('#')[0]; } - public static string GetVShareLink(Server s, string scheme = "vmess") + if (text.Contains("?")) { - // https://github.com/XTLS/Xray-core/issues/91 - var server = (VMessServer)s; - var parameter = new Dictionary(); - // protocol-specific fields - parameter.Add("type", server.TransferProtocol); - parameter.Add("encryption", server.EncryptMethod); - - // transport-specific fields + var parameter = HttpUtility.ParseQueryString(text.Split('?')[1]); + text = text.Substring(0, text.IndexOf("?", StringComparison.Ordinal)); + server.TransferProtocol = parameter.Get("type") ?? "tcp"; + server.EncryptMethod = parameter.Get("encryption") ?? scheme switch { "vless" => "none", _ => "auto" }; switch (server.TransferProtocol) { case "tcp": break; case "kcp": - if (server.FakeType != "none") - parameter.Add("headerType", server.FakeType); - - if (!server.Path.IsNullOrWhiteSpace()) - parameter.Add("seed", Uri.EscapeDataString(server.Path!)); - + server.FakeType = parameter.Get("headerType") ?? "none"; + server.Path = Uri.UnescapeDataString(parameter.Get("seed") ?? ""); break; case "ws": - parameter.Add("path", Uri.EscapeDataString(server.Path.ValueOrDefault() ?? "/")); - if (!server.Host.IsNullOrWhiteSpace()) - parameter.Add("host", Uri.EscapeDataString(server.Host!)); - + server.Path = Uri.UnescapeDataString(parameter.Get("path") ?? "/"); + server.Host = Uri.UnescapeDataString(parameter.Get("host") ?? ""); break; case "h2": - parameter.Add("path", Uri.EscapeDataString(server.Path.ValueOrDefault() ?? "/")); - if (!server.Host.IsNullOrWhiteSpace()) - parameter.Add("host", Uri.EscapeDataString(server.Host!)); - + server.Path = Uri.UnescapeDataString(parameter.Get("path") ?? "/"); + server.Host = Uri.UnescapeDataString(parameter.Get("host") ?? ""); break; case "quic": - if (server.QUICSecure is not (null or "none")) - { - parameter.Add("quicSecurity", server.QUICSecure); - parameter.Add("key", server.QUICSecret!); - } - - if (server.FakeType != "none") - parameter.Add("headerType", server.FakeType); - + server.QUICSecure = parameter.Get("quicSecurity") ?? "none"; + server.QUICSecret = parameter.Get("key") ?? ""; + server.FakeType = parameter.Get("headerType") ?? "none"; break; case "grpc": - if (!string.IsNullOrEmpty(server.Path)) - parameter.Add("serviceName", server.Path); - - if (server.FakeType is "gun" or "multi") - parameter.Add("mode", server.FakeType); - + server.FakeType = parameter.Get("mode") ?? "gun"; + server.Path = parameter.Get("serviceName") ?? ""; break; } + server.TLSSecureType = parameter.Get("security") ?? "none"; if (server.TLSSecureType != "none") { - parameter.Add("security", server.TLSSecureType); - - if (!server.Host.IsNullOrWhiteSpace()) - parameter.Add("sni", server.Host!); - + server.ServerName = parameter.Get("sni") ?? ""; if (server.TLSSecureType == "xtls") - { - var flow = ((VLESSServer)server).Flow; - if (!flow.IsNullOrWhiteSpace()) - parameter.Add("flow", flow!.Replace("-udp443", "")); - } + ((VLESSServer)server).Flow = parameter.Get("flow") ?? ""; } - - return - $"{scheme}://{server.UserID}@{server.Hostname}:{server.Port}?{string.Join("&", parameter.Select(p => $"{p.Key}={p.Value}"))}{(!server.Remark.IsNullOrWhiteSpace() ? $"#{Uri.EscapeDataString(server.Remark)}" : "")}"; } + + var finder = new Regex(@$"^{scheme}://(?.+?)@(?.+):(?\d+)"); + var match = finder.Match(text.Split('?')[0]); + if (!match.Success) + throw new FormatException(); + + server.UserID = match.Groups["guid"].Value; + server.Hostname = match.Groups["server"].Value; + server.Port = ushort.Parse(match.Groups["port"].Value); + + return new[] { server }; + } + + public static string GetVShareLink(Server s, string scheme = "vmess") + { + // https://github.com/XTLS/Xray-core/issues/91 + var server = (VMessServer)s; + var parameter = new Dictionary(); + // protocol-specific fields + parameter.Add("type", server.TransferProtocol); + parameter.Add("encryption", server.EncryptMethod); + + // transport-specific fields + switch (server.TransferProtocol) + { + case "tcp": + break; + case "kcp": + if (server.FakeType != "none") + parameter.Add("headerType", server.FakeType); + + if (!server.Path.IsNullOrWhiteSpace()) + parameter.Add("seed", Uri.EscapeDataString(server.Path!)); + + break; + case "ws": + parameter.Add("path", Uri.EscapeDataString(server.Path.ValueOrDefault() ?? "/")); + if (!server.Host.IsNullOrWhiteSpace()) + parameter.Add("host", Uri.EscapeDataString(server.Host!)); + + break; + case "h2": + parameter.Add("path", Uri.EscapeDataString(server.Path.ValueOrDefault() ?? "/")); + if (!server.Host.IsNullOrWhiteSpace()) + parameter.Add("host", Uri.EscapeDataString(server.Host!)); + + break; + case "quic": + if (server.QUICSecure is not (null or "none")) + { + parameter.Add("quicSecurity", server.QUICSecure); + parameter.Add("key", server.QUICSecret!); + } + + if (server.FakeType != "none") + parameter.Add("headerType", server.FakeType); + + break; + case "grpc": + if (!string.IsNullOrEmpty(server.Path)) + parameter.Add("serviceName", server.Path); + + if (server.FakeType is "gun" or "multi") + parameter.Add("mode", server.FakeType); + + break; + } + + if (server.TLSSecureType != "none") + { + parameter.Add("security", server.TLSSecureType); + + if (!server.Host.IsNullOrWhiteSpace()) + parameter.Add("sni", server.Host!); + + if (server.TLSSecureType == "xtls") + { + var flow = ((VLESSServer)server).Flow; + if (!flow.IsNullOrWhiteSpace()) + parameter.Add("flow", flow!.Replace("-udp443", "")); + } + } + + return + $"{scheme}://{server.UserID}@{server.Hostname}:{server.Port}?{string.Join("&", parameter.Select(p => $"{p.Key}={p.Value}"))}{(!server.Remark.IsNullOrWhiteSpace() ? $"#{Uri.EscapeDataString(server.Remark)}" : "")}"; } } \ No newline at end of file diff --git a/Netch/Servers/VLESS/VLESSForm.cs b/Netch/Servers/VLESS/VLESSForm.cs index 83ac5db2..1f32e7a7 100644 --- a/Netch/Servers/VLESS/VLESSForm.cs +++ b/Netch/Servers/VLESS/VLESSForm.cs @@ -1,43 +1,41 @@ -using System.Collections.Generic; using Netch.Forms; -namespace Netch.Servers +namespace Netch.Servers; + +internal class VLESSForm : ServerForm { - internal class VLESSForm : ServerForm + public VLESSForm(VLESSServer? server = default) { - public VLESSForm(VLESSServer? server = default) - { - server ??= new VLESSServer(); - Server = server; - CreateTextBox("Sni", "ServerName(Sni)", s => true, s => server.ServerName = s, server.ServerName); - CreateTextBox("UUID", "UUID", s => true, s => server.UserID = s, server.UserID); - CreateTextBox("EncryptMethod", - "Encrypt Method", - s => true, - s => server.EncryptMethod = !string.IsNullOrWhiteSpace(s) ? s : "none", - server.EncryptMethod); + server ??= new VLESSServer(); + Server = server; + CreateTextBox("Sni", "ServerName(Sni)", s => true, s => server.ServerName = s, server.ServerName); + CreateTextBox("UUID", "UUID", s => true, s => server.UserID = s, server.UserID); + CreateTextBox("EncryptMethod", + "Encrypt Method", + s => true, + s => server.EncryptMethod = !string.IsNullOrWhiteSpace(s) ? s : "none", + server.EncryptMethod); - CreateTextBox("Flow", "Flow", s => true, s => server.Flow = s, server.Flow); - CreateComboBox("TransferProtocol", - "Transfer Protocol", - VLESSGlobal.TransferProtocols, - s => server.TransferProtocol = s, - server.TransferProtocol); + CreateTextBox("Flow", "Flow", s => true, s => server.Flow = s, server.Flow); + CreateComboBox("TransferProtocol", + "Transfer Protocol", + VLESSGlobal.TransferProtocols, + s => server.TransferProtocol = s, + server.TransferProtocol); - CreateComboBox("FakeType", "Fake Type", VLESSGlobal.FakeTypes, s => server.FakeType = s, server.FakeType); - CreateTextBox("Host", "Host", s => true, s => server.Host = s, server.Host); - CreateTextBox("Path", "Path", s => true, s => server.Path = s, server.Path); - CreateComboBox("QUICSecurity", "QUIC Security", VLESSGlobal.QUIC, s => server.QUICSecure = s, server.QUICSecure); - CreateTextBox("QUICSecret", "QUIC Secret", s => true, s => server.QUICSecret = s, server.QUICSecret); - CreateComboBox("UseMux", - "Use Mux", - new List { "", "true", "false" }, - s => server.UseMux = s switch { "" => null, "true" => true, "false" => false, _ => null }, - server.UseMux?.ToString().ToLower() ?? ""); + CreateComboBox("FakeType", "Fake Type", VLESSGlobal.FakeTypes, s => server.FakeType = s, server.FakeType); + CreateTextBox("Host", "Host", s => true, s => server.Host = s, server.Host); + CreateTextBox("Path", "Path", s => true, s => server.Path = s, server.Path); + CreateComboBox("QUICSecurity", "QUIC Security", VLESSGlobal.QUIC, s => server.QUICSecure = s, server.QUICSecure); + CreateTextBox("QUICSecret", "QUIC Secret", s => true, s => server.QUICSecret = s, server.QUICSecret); + CreateComboBox("UseMux", + "Use Mux", + new List { "", "true", "false" }, + s => server.UseMux = s switch { "" => null, "true" => true, "false" => false, _ => null }, + server.UseMux?.ToString().ToLower() ?? ""); - CreateComboBox("TLSSecure", "TLS Secure", VLESSGlobal.TLSSecure, s => server.TLSSecureType = s, server.TLSSecureType); - } - - protected override string TypeName { get; } = "VLESS"; + CreateComboBox("TLSSecure", "TLS Secure", VLESSGlobal.TLSSecure, s => server.TLSSecureType = s, server.TLSSecureType); } + + protected override string TypeName { get; } = "VLESS"; } \ No newline at end of file diff --git a/Netch/Servers/VLESS/VLESSServer.cs b/Netch/Servers/VLESS/VLESSServer.cs index 9e4d1217..1ffade12 100644 --- a/Netch/Servers/VLESS/VLESSServer.cs +++ b/Netch/Servers/VLESS/VLESSServer.cs @@ -1,44 +1,41 @@ -using System.Collections.Generic; +namespace Netch.Servers; -namespace Netch.Servers +public class VLESSServer : VMessServer { - public class VLESSServer : VMessServer + public override string Type { get; } = "VLESS"; + + /// + /// 加密方式 + /// + public override string EncryptMethod { get; set; } = "none"; + + /// + /// 传输协议 + /// + public override string TransferProtocol { get; set; } = VLESSGlobal.TransferProtocols[0]; + + /// + /// 伪装类型 + /// + public override string FakeType { get; set; } = VLESSGlobal.FakeTypes[0]; + + /// + /// + public string? Flow { get; set; } +} + +public class VLESSGlobal +{ + public static readonly List TLSSecure = new() { - public override string Type { get; } = "VLESS"; + "none", + "tls", + "xtls" + }; - /// - /// 加密方式 - /// - public override string EncryptMethod { get; set; } = "none"; + public static List FakeTypes => VMessGlobal.FakeTypes; - /// - /// 传输协议 - /// - public override string TransferProtocol { get; set; } = VLESSGlobal.TransferProtocols[0]; + public static List TransferProtocols => VMessGlobal.TransferProtocols; - /// - /// 伪装类型 - /// - public override string FakeType { get; set; } = VLESSGlobal.FakeTypes[0]; - - /// - /// - public string? Flow { get; set; } - } - - public class VLESSGlobal - { - public static readonly List TLSSecure = new() - { - "none", - "tls", - "xtls" - }; - - public static List FakeTypes => VMessGlobal.FakeTypes; - - public static List TransferProtocols => VMessGlobal.TransferProtocols; - - public static List QUIC => VMessGlobal.QUIC; - } + public static List QUIC => VMessGlobal.QUIC; } \ No newline at end of file diff --git a/Netch/Servers/VLESS/VLESSUtil.cs b/Netch/Servers/VLESS/VLESSUtil.cs index dbe245ce..527d5c2a 100644 --- a/Netch/Servers/VLESS/VLESSUtil.cs +++ b/Netch/Servers/VLESS/VLESSUtil.cs @@ -1,52 +1,49 @@ -using System; -using System.Collections.Generic; using Netch.Interfaces; using Netch.Models; -namespace Netch.Servers +namespace Netch.Servers; + +public class VLESSUtil : IServerUtil { - public class VLESSUtil : IServerUtil + public ushort Priority { get; } = 2; + + public string TypeName { get; } = "VLESS"; + + public string FullName { get; } = "VLESS"; + + public string ShortName { get; } = "VL"; + + public string[] UriScheme { get; } = { "vless" }; + + public Type ServerType { get; } = typeof(VLESSServer); + + public void Edit(Server s) { - public ushort Priority { get; } = 2; + new VLESSForm((VLESSServer)s).ShowDialog(); + } - public string TypeName { get; } = "VLESS"; + public void Create() + { + new VLESSForm().ShowDialog(); + } - public string FullName { get; } = "VLESS"; + public string GetShareLink(Server s) + { + return V2rayUtils.GetVShareLink(s, "vless"); + } - public string ShortName { get; } = "VL"; + public IServerController GetController() + { + return new V2rayController(); + } - public string[] UriScheme { get; } = { "vless" }; + public IEnumerable ParseUri(string text) + { + return V2rayUtils.ParseVUri(text); + } - public Type ServerType { get; } = typeof(VLESSServer); - - public void Edit(Server s) - { - new VLESSForm((VLESSServer)s).ShowDialog(); - } - - public void Create() - { - new VLESSForm().ShowDialog(); - } - - public string GetShareLink(Server s) - { - return V2rayUtils.GetVShareLink(s, "vless"); - } - - public IServerController GetController() - { - return new V2rayController(); - } - - public IEnumerable ParseUri(string text) - { - return V2rayUtils.ParseVUri(text); - } - - public bool CheckServer(Server s) - { - return true; - } + public bool CheckServer(Server s) + { + return true; } } \ No newline at end of file diff --git a/Netch/Servers/VMess/VMessForm.cs b/Netch/Servers/VMess/VMessForm.cs index e11d90bb..00a76b64 100644 --- a/Netch/Servers/VMess/VMessForm.cs +++ b/Netch/Servers/VMess/VMessForm.cs @@ -1,38 +1,36 @@ -using System.Collections.Generic; -using Netch.Forms; +using Netch.Forms; -namespace Netch.Servers +namespace Netch.Servers; + +public class VMessForm : ServerForm { - public class VMessForm : ServerForm + public VMessForm(VMessServer? server = default) { - public VMessForm(VMessServer? server = default) - { - server ??= new VMessServer(); - Server = server; - CreateTextBox("Sni", "ServerName(Sni)", s => true, s => server.ServerName = s, server.ServerName); - CreateTextBox("UserId", "User ID", s => true, s => server.UserID = s, server.UserID); - CreateTextBox("AlterId", "Alter ID", s => int.TryParse(s, out _), s => server.AlterID = int.Parse(s), server.AlterID.ToString(), 76); - CreateComboBox("EncryptMethod", "Encrypt Method", VMessGlobal.EncryptMethods, s => server.EncryptMethod = s, server.EncryptMethod); - CreateComboBox("TransferProtocol", - "Transfer Protocol", - VMessGlobal.TransferProtocols, - s => server.TransferProtocol = s, - server.TransferProtocol); + server ??= new VMessServer(); + Server = server; + CreateTextBox("Sni", "ServerName(Sni)", s => true, s => server.ServerName = s, server.ServerName); + CreateTextBox("UserId", "User ID", s => true, s => server.UserID = s, server.UserID); + CreateTextBox("AlterId", "Alter ID", s => int.TryParse(s, out _), s => server.AlterID = int.Parse(s), server.AlterID.ToString(), 76); + CreateComboBox("EncryptMethod", "Encrypt Method", VMessGlobal.EncryptMethods, s => server.EncryptMethod = s, server.EncryptMethod); + CreateComboBox("TransferProtocol", + "Transfer Protocol", + VMessGlobal.TransferProtocols, + s => server.TransferProtocol = s, + server.TransferProtocol); - CreateComboBox("FakeType", "Fake Type", VMessGlobal.FakeTypes, s => server.FakeType = s, server.FakeType); - CreateTextBox("Host", "Host", s => true, s => server.Host = s, server.Host); - CreateTextBox("Path", "Path", s => true, s => server.Path = s, server.Path); - CreateComboBox("QUICSecurity", "QUIC Security", VMessGlobal.QUIC, s => server.QUICSecure = s, server.QUICSecure); - CreateTextBox("QUICSecret", "QUIC Secret", s => true, s => server.QUICSecret = s, server.QUICSecret); - CreateComboBox("UseMux", - "Use Mux", - new List { "", "true", "false" }, - s => server.UseMux = s switch { "" => null, "true" => true, "false" => false, _ => null }, - server.UseMux?.ToString().ToLower() ?? ""); + CreateComboBox("FakeType", "Fake Type", VMessGlobal.FakeTypes, s => server.FakeType = s, server.FakeType); + CreateTextBox("Host", "Host", s => true, s => server.Host = s, server.Host); + CreateTextBox("Path", "Path", s => true, s => server.Path = s, server.Path); + CreateComboBox("QUICSecurity", "QUIC Security", VMessGlobal.QUIC, s => server.QUICSecure = s, server.QUICSecure); + CreateTextBox("QUICSecret", "QUIC Secret", s => true, s => server.QUICSecret = s, server.QUICSecret); + CreateComboBox("UseMux", + "Use Mux", + new List { "", "true", "false" }, + s => server.UseMux = s switch { "" => null, "true" => true, "false" => false, _ => null }, + server.UseMux?.ToString().ToLower() ?? ""); - CreateComboBox("TLSSecure", "TLS Secure", VMessGlobal.TLSSecure, s => server.TLSSecureType = s, server.TLSSecureType); - } - - protected override string TypeName { get; } = "VMess"; + CreateComboBox("TLSSecure", "TLS Secure", VMessGlobal.TLSSecure, s => server.TLSSecureType = s, server.TLSSecureType); } + + protected override string TypeName { get; } = "VMess"; } \ No newline at end of file diff --git a/Netch/Servers/VMess/VMessServer.cs b/Netch/Servers/VMess/VMessServer.cs index 11f060a9..b2783f23 100644 --- a/Netch/Servers/VMess/VMessServer.cs +++ b/Netch/Servers/VMess/VMessServer.cs @@ -1,156 +1,154 @@ -using System.Collections.Generic; using Netch.Models; -namespace Netch.Servers +namespace Netch.Servers; + +public class VMessServer : Server { - public class VMessServer : Server + private string _tlsSecureType = VMessGlobal.TLSSecure[0]; + + public override string Type { get; } = "VMess"; + + public override string MaskedData() { - private string _tlsSecureType = VMessGlobal.TLSSecure[0]; - - public override string Type { get; } = "VMess"; - - public override string MaskedData() + var maskedData = $"{EncryptMethod} + {TransferProtocol} + {FakeType}"; + switch (TransferProtocol) { - var maskedData = $"{EncryptMethod} + {TransferProtocol} + {FakeType}"; - switch (TransferProtocol) - { - case "tcp": - case "ws": - maskedData += $" + {TLSSecureType}"; - break; - case "quic": - maskedData += $" + {QUICSecure}"; - break; - case "grpc": - break; - case "kcp": - break; - } - - return maskedData; + case "tcp": + case "ws": + maskedData += $" + {TLSSecureType}"; + break; + case "quic": + maskedData += $" + {QUICSecure}"; + break; + case "grpc": + break; + case "kcp": + break; } - /// - /// 用户 ID - /// - public string UserID { get; set; } = string.Empty; - - /// - /// 额外 ID - /// - public int AlterID { get; set; } - - /// - /// 加密方式 - /// - public virtual string EncryptMethod { get; set; } = VMessGlobal.EncryptMethods[0]; - - /// - /// 传输协议 - /// - public virtual string TransferProtocol { get; set; } = VMessGlobal.TransferProtocols[0]; - - /// - /// 伪装类型 - /// - public virtual string FakeType { get; set; } = VMessGlobal.FakeTypes[0]; - - /// - /// 伪装域名 - /// - public string? Host { get; set; } - - /// - /// 传输路径 - /// - public string? Path { get; set; } - - /// - /// QUIC 加密方式 - /// - public string? QUICSecure { get; set; } = VMessGlobal.QUIC[0]; - - /// - /// QUIC 加密密钥 - /// - public string? QUICSecret { get; set; } = string.Empty; - - /// - /// TLS 底层传输安全 - /// - public string TLSSecureType - { - get => _tlsSecureType; - set - { - if (value == "") - value = "none"; - - _tlsSecureType = value; - } - } - - /// - /// Mux 多路复用 - /// - public bool? UseMux { get; set; } - - public string? ServerName { get; set; } = string.Empty; + return maskedData; } - public class VMessGlobal + /// + /// 用户 ID + /// + public string UserID { get; set; } = string.Empty; + + /// + /// 额外 ID + /// + public int AlterID { get; set; } + + /// + /// 加密方式 + /// + public virtual string EncryptMethod { get; set; } = VMessGlobal.EncryptMethods[0]; + + /// + /// 传输协议 + /// + public virtual string TransferProtocol { get; set; } = VMessGlobal.TransferProtocols[0]; + + /// + /// 伪装类型 + /// + public virtual string FakeType { get; set; } = VMessGlobal.FakeTypes[0]; + + /// + /// 伪装域名 + /// + public string? Host { get; set; } + + /// + /// 传输路径 + /// + public string? Path { get; set; } + + /// + /// QUIC 加密方式 + /// + public string? QUICSecure { get; set; } = VMessGlobal.QUIC[0]; + + /// + /// QUIC 加密密钥 + /// + public string? QUICSecret { get; set; } = string.Empty; + + /// + /// TLS 底层传输安全 + /// + public string TLSSecureType { - public static readonly List EncryptMethods = new() + get => _tlsSecureType; + set { - "auto", - "none", - "aes-128-gcm", - "chacha20-poly1305" - }; + if (value == "") + value = "none"; - public static readonly List QUIC = new() - { - "none", - "aes-128-gcm", - "chacha20-poly1305" - }; - - /// - /// V2Ray 传输协议 - /// - public static readonly List TransferProtocols = new() - { - "tcp", - "kcp", - "ws", - "h2", - "quic", - "grpc" - }; - - /// - /// V2Ray 伪装类型 - /// - public static readonly List FakeTypes = new() - { - "none", - "http", - "srtp", - "utp", - "wechat-video", - "dtls", - "wireguard", - "gun", - "multi" - }; - - /// - /// TLS 安全类型 - /// - public static readonly List TLSSecure = new() - { - "none", - "tls" - }; + _tlsSecureType = value; + } } + + /// + /// Mux 多路复用 + /// + public bool? UseMux { get; set; } + + public string? ServerName { get; set; } = string.Empty; +} + +public class VMessGlobal +{ + public static readonly List EncryptMethods = new() + { + "auto", + "none", + "aes-128-gcm", + "chacha20-poly1305" + }; + + public static readonly List QUIC = new() + { + "none", + "aes-128-gcm", + "chacha20-poly1305" + }; + + /// + /// V2Ray 传输协议 + /// + public static readonly List TransferProtocols = new() + { + "tcp", + "kcp", + "ws", + "h2", + "quic", + "grpc" + }; + + /// + /// V2Ray 伪装类型 + /// + public static readonly List FakeTypes = new() + { + "none", + "http", + "srtp", + "utp", + "wechat-video", + "dtls", + "wireguard", + "gun", + "multi" + }; + + /// + /// TLS 安全类型 + /// + public static readonly List TLSSecure = new() + { + "none", + "tls" + }; } \ No newline at end of file diff --git a/Netch/Servers/VMess/VMessUtil.cs b/Netch/Servers/VMess/VMessUtil.cs index 9738b64c..2578d4db 100644 --- a/Netch/Servers/VMess/VMessUtil.cs +++ b/Netch/Servers/VMess/VMessUtil.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; @@ -7,116 +5,115 @@ using Netch.Interfaces; using Netch.Models; using Netch.Utils; -namespace Netch.Servers +namespace Netch.Servers; + +public class VMessUtil : IServerUtil { - public class VMessUtil : IServerUtil + public ushort Priority { get; } = 3; + + public string TypeName { get; } = "VMess"; + + public string FullName { get; } = "VMess"; + + public string ShortName { get; } = "V2"; + + public string[] UriScheme { get; } = { "vmess" }; + + public Type ServerType { get; } = typeof(VMessServer); + + public void Edit(Server s) { - public ushort Priority { get; } = 3; + new VMessForm((VMessServer)s).ShowDialog(); + } - public string TypeName { get; } = "VMess"; + public void Create() + { + new VMessForm().ShowDialog(); + } - public string FullName { get; } = "VMess"; - - public string ShortName { get; } = "V2"; - - public string[] UriScheme { get; } = { "vmess" }; - - public Type ServerType { get; } = typeof(VMessServer); - - public void Edit(Server s) + public string GetShareLink(Server s) + { + if (Global.Settings.V2RayConfig.V2rayNShareLink) { - new VMessForm((VMessServer)s).ShowDialog(); + var server = (VMessServer)s; + + var vmessJson = JsonSerializer.Serialize(new V2rayNJObject + { + v = 2, + ps = server.Remark, + add = server.Hostname, + port = server.Port, + scy = server.EncryptMethod, + id = server.UserID, + aid = server.AlterID, + net = server.TransferProtocol, + type = server.FakeType, + host = server.Host ?? "", + path = server.Path ?? "", + tls = server.TLSSecureType, + sni = server.ServerName ?? "" + }, + new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }); + + return "vmess://" + ShareLink.URLSafeBase64Encode(vmessJson); } - public void Create() + return V2rayUtils.GetVShareLink(s); + } + + public IServerController GetController() + { + return new V2rayController(); + } + + public IEnumerable ParseUri(string text) + { + var data = new VMessServer(); + + string s; + try { - new VMessForm().ShowDialog(); + s = ShareLink.URLSafeBase64Decode(text.Substring(8)); + } + catch + { + return V2rayUtils.ParseVUri(text); } - public string GetShareLink(Server s) + V2rayNJObject vmess = JsonSerializer.Deserialize(s, + new JsonSerializerOptions { NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString })!; + + data.Remark = vmess.ps; + data.Hostname = vmess.add; + data.EncryptMethod = vmess.scy; + data.Port = vmess.port; + data.UserID = vmess.id; + data.AlterID = vmess.aid; + data.TransferProtocol = vmess.net; + data.FakeType = vmess.type; + data.ServerName = vmess.sni; + + if (data.TransferProtocol == "quic") { - if (Global.Settings.V2RayConfig.V2rayNShareLink) - { - var server = (VMessServer)s; - - var vmessJson = JsonSerializer.Serialize(new V2rayNJObject - { - v = 2, - ps = server.Remark, - add = server.Hostname, - port = server.Port, - scy = server.EncryptMethod, - id = server.UserID, - aid = server.AlterID, - net = server.TransferProtocol, - type = server.FakeType, - host = server.Host ?? "", - path = server.Path ?? "", - tls = server.TLSSecureType, - sni = server.ServerName ?? "" - }, - new JsonSerializerOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping - }); - - return "vmess://" + ShareLink.URLSafeBase64Encode(vmessJson); - } - - return V2rayUtils.GetVShareLink(s); + data.QUICSecure = vmess.host; + data.QUICSecret = vmess.path; + } + else + { + data.Host = vmess.host; + data.Path = vmess.path; } - public IServerController GetController() - { - return new V2rayController(); - } + data.TLSSecureType = vmess.tls; - public IEnumerable ParseUri(string text) - { - var data = new VMessServer(); + return new[] { data }; + } - string s; - try - { - s = ShareLink.URLSafeBase64Decode(text.Substring(8)); - } - catch - { - return V2rayUtils.ParseVUri(text); - } - - V2rayNJObject vmess = JsonSerializer.Deserialize(s, - new JsonSerializerOptions { NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString })!; - - data.Remark = vmess.ps; - data.Hostname = vmess.add; - data.EncryptMethod = vmess.scy; - data.Port = vmess.port; - data.UserID = vmess.id; - data.AlterID = vmess.aid; - data.TransferProtocol = vmess.net; - data.FakeType = vmess.type; - data.ServerName = vmess.sni; - - if (data.TransferProtocol == "quic") - { - data.QUICSecure = vmess.host; - data.QUICSecret = vmess.path; - } - else - { - data.Host = vmess.host; - data.Path = vmess.path; - } - - data.TLSSecureType = vmess.tls; - - return new[] { data }; - } - - public bool CheckServer(Server s) - { - return true; - } + public bool CheckServer(Server s) + { + return true; } } \ No newline at end of file diff --git a/Netch/Services/ModeService.cs b/Netch/Services/ModeService.cs index 5a0b520e..6a8a6320 100644 --- a/Netch/Services/ModeService.cs +++ b/Netch/Services/ModeService.cs @@ -1,121 +1,117 @@ -using System; -using System.IO; -using Netch.Controllers; +using Netch.Controllers; using Netch.Interfaces; using Netch.Models; using Netch.Models.Modes; using Netch.Utils; -using Serilog; -namespace Netch.Services +namespace Netch.Services; + +public class ModeService { - public class ModeService + public static readonly ModeService Instance = new(); + + public string ModeDirectoryFullName => Path.Combine(Global.NetchDir, "mode"); + + public string GetRelativePath(string fullName) { - public static readonly ModeService Instance = new(); + var length = ModeDirectoryFullName.Length; + if (!ModeDirectoryFullName.EndsWith("\\")) + length++; - public string ModeDirectoryFullName => Path.Combine(Global.NetchDir, "mode"); + return fullName.Substring(length); + } - public string GetRelativePath(string fullName) + public string GetFullPath(string relativeName) + { + return Path.Combine(ModeDirectoryFullName, relativeName); + } + + public void Load() + { + Global.Modes.Clear(); + LoadCore(ModeDirectoryFullName); + Sort(); + Global.MainForm.LoadModes(); + } + + private void LoadCore(string modeDirectory) + { + foreach (var directory in Directory.GetDirectories(modeDirectory)) + LoadCore(directory); + + // skip Directory with a disabled file in + if (File.Exists(Path.Combine(modeDirectory, Constants.DisableModeDirectoryFileName))) + return; + + foreach (var file in Directory.GetFiles(modeDirectory)) { - var length = ModeDirectoryFullName.Length; - if (!ModeDirectoryFullName.EndsWith("\\")) - length++; - - return fullName.Substring(length); - } - - public string GetFullPath(string relativeName) - { - return Path.Combine(ModeDirectoryFullName, relativeName); - } - - public void Load() - { - Global.Modes.Clear(); - LoadCore(ModeDirectoryFullName); - Sort(); - Global.MainForm.LoadModes(); - } - - private void LoadCore(string modeDirectory) - { - foreach (var directory in Directory.GetDirectories(modeDirectory)) - LoadCore(directory); - - // skip Directory with a disabled file in - if (File.Exists(Path.Combine(modeDirectory, Constants.DisableModeDirectoryFileName))) - return; - - foreach (var file in Directory.GetFiles(modeDirectory)) + try { - try - { - Global.Modes.Add(ModeHelper.LoadMode(file)); - } - catch (NotSupportedException) - { - // ignored - } - catch (Exception e) - { - Log.Warning(e, "Load mode \"{FileName}\" failed", file); - } + Global.Modes.Add(ModeHelper.LoadMode(file)); } - } - - private static void SortCollection() - { - // TODO better sort need to discuss - // TODO replace Mode Collection type - Global.Modes.Sort((a, b) => string.Compare(a.i18NRemark, b.i18NRemark, StringComparison.Ordinal)); - } - - public void Add(Mode mode) - { - if (mode.FullName == null) - throw new InvalidOperationException(); - - Global.Modes.Add(mode); - Sort(); - Global.MainForm.ModeComboBox.Items.Insert(Global.Modes.IndexOf(mode), mode); - - mode.WriteFile(); - } - - public void Sort() - { - SortCollection(); - Global.MainForm.LoadModes(); - } - - public static void Delete(Mode mode) - { - if (mode.FullName == null) - throw new ArgumentException(nameof(mode.FullName)); - - Global.MainForm.ModeComboBox.Items.Remove(mode); - Global.Modes.Remove(mode); - - if (File.Exists(mode.FullName)) - File.Delete(mode.FullName); - } - - public static IModeController GetModeControllerByType(ModeType type, out ushort? port, out string portName) - { - port = null; - portName = string.Empty; - switch (type) + catch (NotSupportedException) { - case ModeType.ProcessMode: - return new NFController(); - case ModeType.TunMode: - return new TUNController(); - case ModeType.ShareMode: - return new PcapController(); - default: - Log.Error("Unknown Mode Type \"{Type}\"", (int)type); - throw new MessageException("Unknown Mode Type"); + // ignored + } + catch (Exception e) + { + Log.Warning(e, "Load mode \"{FileName}\" failed", file); } } } + + private static void SortCollection() + { + // TODO better sort need to discuss + // TODO replace Mode Collection type + Global.Modes.Sort((a, b) => string.Compare(a.i18NRemark, b.i18NRemark, StringComparison.Ordinal)); + } + + public void Add(Mode mode) + { + if (mode.FullName == null) + throw new InvalidOperationException(); + + Global.Modes.Add(mode); + Sort(); + Global.MainForm.ModeComboBox.Items.Insert(Global.Modes.IndexOf(mode), mode); + + mode.WriteFile(); + } + + public void Sort() + { + SortCollection(); + Global.MainForm.LoadModes(); + } + + public static void Delete(Mode mode) + { + if (mode.FullName == null) + throw new ArgumentException(nameof(mode.FullName)); + + Global.MainForm.ModeComboBox.Items.Remove(mode); + Global.Modes.Remove(mode); + + if (File.Exists(mode.FullName)) + File.Delete(mode.FullName); + } + + public static IModeController GetModeControllerByType(ModeType type, out ushort? port, out string portName) + { + port = null; + portName = string.Empty; + switch (type) + { + case ModeType.ProcessMode: + return new NFController(); + case ModeType.TunMode: + return new TUNController(); + case ModeType.ShareMode: + return new PcapController(); + default: + Log.Error("Unknown Mode Type \"{Type}\"", (int)type); + throw new MessageException("Unknown Mode Type"); + } + } } \ No newline at end of file diff --git a/Netch/Services/Updater.cs b/Netch/Services/Updater.cs index 139271dd..8244a6a0 100644 --- a/Netch/Services/Updater.cs +++ b/Netch/Services/Updater.cs @@ -1,140 +1,135 @@ -using System; using System.Collections.Immutable; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Text; using Netch.Models; using Netch.Properties; using Netch.Utils; -using Serilog; -namespace Netch.Services +namespace Netch.Services; + +public class Updater { - public class Updater + private string UpdateFile { get; } + + private string InstallDirectory { get; } + + private readonly string _tempDirectory; + private static readonly string[] KeepDirectories = { "data", "mode\\Custom", "logging" }; + private static readonly string[] KeepFiles = { Constants.DisableModeDirectoryFileName }; + + internal Updater(string updateFile, string installDirectory) { - private string UpdateFile { get; } + UpdateFile = updateFile; + InstallDirectory = installDirectory; + _tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - private string InstallDirectory { get; } - - private readonly string _tempDirectory; - private static readonly string[] KeepDirectories = { "data", "mode\\Custom", "logging" }; - private static readonly string[] KeepFiles = { Constants.DisableModeDirectoryFileName }; - - internal Updater(string updateFile, string installDirectory) - { - UpdateFile = updateFile; - InstallDirectory = installDirectory; - _tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - - Directory.CreateDirectory(_tempDirectory); - } - - #region Apply Update - - internal void ApplyUpdate() - { - var extractPath = Path.Combine(_tempDirectory, "extract"); - - int exitCode; - if ((exitCode = Extract(extractPath)) != 0) - throw new MessageException(i18N.Translate($"7za unexpectedly exited. ({exitCode})")); - - // update archive file must have a top-level directory "Netch" - var updateDirectory = Path.Combine(extractPath, "Netch"); - if (!Directory.Exists(updateDirectory)) - throw new MessageException(i18N.Translate("Update file top-level directory not exist")); - - // {_tempDirectory}/extract/Netch/Netch.exe - var updateMainProgramFilePath = Path.Combine(updateDirectory, "Netch.exe"); - if (!File.Exists(updateMainProgramFilePath)) - throw new MessageException(i18N.Translate($"Update file main program not exist")); - - MarkFilesOld(); - - // move {tempDirectory}\extract\Netch to install folder - MoveFilesOver(updateDirectory, InstallDirectory); - } - - private void MarkFilesOld() - { - var keepDirFullPath = KeepDirectories.Select(d => Path.Combine(InstallDirectory, d)).ToImmutableList(); - - foreach (var file in Directory.GetFiles(InstallDirectory, "*", SearchOption.AllDirectories)) - { - if (keepDirFullPath.Any(p => file.StartsWith(p))) - continue; - - if (KeepFiles.Contains(Path.GetFileName(file))) - continue; - - try - { - File.Move(file, file + ".old"); - } - catch (Exception e) - { - Log.Error(e, "failed to rename file \"{File}\"", file); - throw; - } - } - } - - private int Extract(string destDirName) - { - // release 7za.exe to {tempDirectory}\7za.exe - var temp7za = Path.Combine(_tempDirectory, "7za.exe"); - - if (!File.Exists(temp7za)) - File.WriteAllBytes(temp7za, Resources._7za); - - var argument = new StringBuilder($" x \"{UpdateFile}\" -o\"{destDirName}\" -y"); - var process = Process.Start(new ProcessStartInfo(temp7za, argument.ToString()) - { - UseShellExecute = false - })!; - - process.WaitForExit(); - return process.ExitCode; - } - - private static void MoveFilesOver(string source, string target) - { - foreach (string directory in Directory.GetDirectories(source)) - { - string dirName = Path.GetFileName(directory); - - if (!Directory.Exists(Path.Combine(target, dirName))) - Directory.CreateDirectory(Path.Combine(target, dirName)); - - MoveFilesOver(directory, Path.Combine(target, dirName)); - } - - foreach (string file in Directory.GetFiles(source)) - { - var destFile = Path.Combine(target, Path.GetFileName(file)); - File.Delete(destFile); - File.Move(file, destFile); - } - } - - #endregion - - #region Clean files marked as old when start - - public static void CleanOld(string targetPath) - { - foreach (var f in Directory.GetFiles(targetPath, "*.old", SearchOption.AllDirectories)) - try - { - File.Delete(f); - } - catch (Exception) - { - // ignored - } - } - - #endregion + Directory.CreateDirectory(_tempDirectory); } + + #region Apply Update + + internal void ApplyUpdate() + { + var extractPath = Path.Combine(_tempDirectory, "extract"); + + int exitCode; + if ((exitCode = Extract(extractPath)) != 0) + throw new MessageException(i18N.Translate($"7za unexpectedly exited. ({exitCode})")); + + // update archive file must have a top-level directory "Netch" + var updateDirectory = Path.Combine(extractPath, "Netch"); + if (!Directory.Exists(updateDirectory)) + throw new MessageException(i18N.Translate("Update file top-level directory not exist")); + + // {_tempDirectory}/extract/Netch/Netch.exe + var updateMainProgramFilePath = Path.Combine(updateDirectory, "Netch.exe"); + if (!File.Exists(updateMainProgramFilePath)) + throw new MessageException(i18N.Translate($"Update file main program not exist")); + + MarkFilesOld(); + + // move {tempDirectory}\extract\Netch to install folder + MoveFilesOver(updateDirectory, InstallDirectory); + } + + private void MarkFilesOld() + { + var keepDirFullPath = KeepDirectories.Select(d => Path.Combine(InstallDirectory, d)).ToImmutableList(); + + foreach (var file in Directory.GetFiles(InstallDirectory, "*", SearchOption.AllDirectories)) + { + if (keepDirFullPath.Any(p => file.StartsWith(p))) + continue; + + if (KeepFiles.Contains(Path.GetFileName(file))) + continue; + + try + { + File.Move(file, file + ".old"); + } + catch (Exception e) + { + Log.Error(e, "failed to rename file \"{File}\"", file); + throw; + } + } + } + + private int Extract(string destDirName) + { + // release 7za.exe to {tempDirectory}\7za.exe + var temp7za = Path.Combine(_tempDirectory, "7za.exe"); + + if (!File.Exists(temp7za)) + File.WriteAllBytes(temp7za, Resources._7za); + + var argument = new StringBuilder($" x \"{UpdateFile}\" -o\"{destDirName}\" -y"); + var process = Process.Start(new ProcessStartInfo(temp7za, argument.ToString()) + { + UseShellExecute = false + })!; + + process.WaitForExit(); + return process.ExitCode; + } + + private static void MoveFilesOver(string source, string target) + { + foreach (string directory in Directory.GetDirectories(source)) + { + string dirName = Path.GetFileName(directory); + + if (!Directory.Exists(Path.Combine(target, dirName))) + Directory.CreateDirectory(Path.Combine(target, dirName)); + + MoveFilesOver(directory, Path.Combine(target, dirName)); + } + + foreach (string file in Directory.GetFiles(source)) + { + var destFile = Path.Combine(target, Path.GetFileName(file)); + File.Delete(destFile); + File.Move(file, destFile); + } + } + + #endregion + + #region Clean files marked as old when start + + public static void CleanOld(string targetPath) + { + foreach (var f in Directory.GetFiles(targetPath, "*.old", SearchOption.AllDirectories)) + try + { + File.Delete(f); + } + catch (Exception) + { + // ignored + } + } + + #endregion } \ No newline at end of file diff --git a/Netch/Utils/Bandwidth.cs b/Netch/Utils/Bandwidth.cs index d61c3806..c23a5cd0 100644 --- a/Netch/Utils/Bandwidth.cs +++ b/Netch/Utils/Bandwidth.cs @@ -1,133 +1,127 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using System.Diagnostics; using Microsoft.Diagnostics.Tracing.Parsers; using Microsoft.Diagnostics.Tracing.Session; using Microsoft.VisualStudio.Threading; using Netch.Controllers; using Netch.Enums; -using Serilog; -namespace Netch.Utils +namespace Netch.Utils; + +public static class Bandwidth { - public static class Bandwidth + public static ulong received; + public static TraceEventSession? tSession; + + private static readonly string[] Suffix = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" }; + + /// + /// 计算流量 + /// + /// + /// 带单位的流量字符串 + public static string Compute(ulong d) { - public static ulong received; - public static TraceEventSession? tSession; + const double step = 1024.00; - private static readonly string[] Suffix = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" }; - - /// - /// 计算流量 - /// - /// - /// 带单位的流量字符串 - public static string Compute(ulong d) + byte level = 0; + double? size = null; + while ((size ?? d) > step) { - const double step = 1024.00; + if (level >= 6) // Suffix.Length - 1 + break; - byte level = 0; - double? size = null; - while ((size ?? d) > step) - { - if (level >= 6) // Suffix.Length - 1 - break; - - level++; - size = (size ?? d) / step; - } - - return $@"{size ?? 0:0.##} {Suffix[level]}"; + level++; + size = (size ?? d) / step; } - /// - /// 根据程序名统计流量 - /// - public static void NetTraffic() + return $@"{size ?? 0:0.##} {Suffix[level]}"; + } + + /// + /// 根据程序名统计流量 + /// + public static void NetTraffic() + { + if (!Flags.IsWindows10Upper) + return; + + var counterLock = new object(); + //int sent = 0; + + var processes = new List(); + switch (MainController.ServerController) { - if (!Flags.IsWindows10Upper) - return; + case null: + break; + case Guard guard: + processes.Add(guard.Instance); + break; + } - var counterLock = new object(); - //int sent = 0; - - var processes = new List(); - switch (MainController.ServerController) + if (!processes.Any()) + switch (MainController.ModeController) { case null: break; + case NFController or TUNController: + processes.Add(Process.GetCurrentProcess()); + break; case Guard guard: processes.Add(guard.Instance); break; } - if (!processes.Any()) - switch (MainController.ModeController) - { - case null: - break; - case NFController or TUNController: - processes.Add(Process.GetCurrentProcess()); - break; - case Guard guard: - processes.Add(guard.Instance); - break; - } + var pidHastSet = processes.Select(instance => instance.Id).ToHashSet(); - var pidHastSet = processes.Select(instance => instance.Id).ToHashSet(); + Log.Information("Net traffic processes: {Processes}", string.Join(',', processes.Select(v => $"({v.Id}){v.ProcessName}"))); - Log.Information("Net traffic processes: {Processes}", string.Join(',', processes.Select(v => $"({v.Id}){v.ProcessName}"))); + received = 0; - received = 0; + if (!processes.Any()) + return; - if (!processes.Any()) - return; + Global.MainForm.BandwidthState(true); - Global.MainForm.BandwidthState(true); - - Task.Run(() => - { - tSession = new TraceEventSession("KernelAndClrEventsSession"); - tSession.EnableKernelProvider(KernelTraceEventParser.Keywords.NetworkTCPIP); - - //这玩意儿上传和下载得到的data是一样的:) - //所以暂时没办法区分上传下载流量 - tSession.Source.Kernel.TcpIpRecv += data => - { - if (pidHastSet.Contains(data.ProcessID)) - lock (counterLock) - received += (ulong)data.size; - - // Debug.WriteLine($"TcpIpRecv: {ToByteSize(data.size)}"); - }; - - tSession.Source.Kernel.UdpIpRecv += data => - { - if (pidHastSet.Contains(data.ProcessID)) - lock (counterLock) - received += (ulong)data.size; - - // Debug.WriteLine($"UdpIpRecv: {ToByteSize(data.size)}"); - }; - - tSession.Source.Process(); - }) - .Forget(); - - while (Global.MainForm.State != State.Stopped) + Task.Run(() => { - Thread.Sleep(1000); - lock (counterLock) - Global.MainForm.OnBandwidthUpdated(received); - } - } + tSession = new TraceEventSession("KernelAndClrEventsSession"); + tSession.EnableKernelProvider(KernelTraceEventParser.Keywords.NetworkTCPIP); - public static void Stop() + //这玩意儿上传和下载得到的data是一样的:) + //所以暂时没办法区分上传下载流量 + tSession.Source.Kernel.TcpIpRecv += data => + { + if (pidHastSet.Contains(data.ProcessID)) + lock (counterLock) + received += (ulong)data.size; + + // Debug.WriteLine($"TcpIpRecv: {ToByteSize(data.size)}"); + }; + + tSession.Source.Kernel.UdpIpRecv += data => + { + if (pidHastSet.Contains(data.ProcessID)) + lock (counterLock) + received += (ulong)data.size; + + // Debug.WriteLine($"UdpIpRecv: {ToByteSize(data.size)}"); + }; + + tSession.Source.Process(); + }) + .Forget(); + + while (Global.MainForm.State != State.Stopped) { - tSession?.Dispose(); - received = 0; + Thread.Sleep(1000); + lock (counterLock) + Global.MainForm.OnBandwidthUpdated(received); } } + + public static void Stop() + { + tSession?.Dispose(); + received = 0; + } } \ No newline at end of file diff --git a/Netch/Utils/Configuration.cs b/Netch/Utils/Configuration.cs index 33a52836..e5ab58aa 100644 --- a/Netch/Utils/Configuration.cs +++ b/Netch/Utils/Configuration.cs @@ -1,138 +1,132 @@ -using System; -using System.IO; -using System.Linq; -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; -using System.Threading.Tasks; using Microsoft.VisualStudio.Threading; using Netch.JsonConverter; using Netch.Models; -using Serilog; -namespace Netch.Utils +namespace Netch.Utils; + +public static class Configuration { - public static class Configuration + /// + /// 数据目录 + /// + public static string DataDirectoryFullName => Path.Combine(Global.NetchDir, "data"); + + public static string FileFullName => Path.Combine(DataDirectoryFullName, FileName); + + private static string BackupFileFullName => Path.Combine(DataDirectoryFullName, BackupFileName); + + private const string FileName = "settings.json"; + + private const string BackupFileName = "settings.json.bak"; + + private static readonly AsyncReaderWriterLock _lock = new(null); + + private static readonly JsonSerializerOptions JsonSerializerOptions = Global.NewCustomJsonSerializerOptions(); + + static Configuration() { - /// - /// 数据目录 - /// - public static string DataDirectoryFullName => Path.Combine(Global.NetchDir, "data"); + JsonSerializerOptions.Converters.Add(new ServerConverterWithTypeDiscriminator()); + JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + } - public static string FileFullName => Path.Combine(DataDirectoryFullName, FileName); - - private static string BackupFileFullName => Path.Combine(DataDirectoryFullName, BackupFileName); - - private const string FileName = "settings.json"; - - private const string BackupFileName = "settings.json.bak"; - - private static readonly AsyncReaderWriterLock _lock = new(null); - - private static readonly JsonSerializerOptions JsonSerializerOptions = Global.NewCustomJsonSerializerOptions(); - - static Configuration() - { - JsonSerializerOptions.Converters.Add(new ServerConverterWithTypeDiscriminator()); - JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); - } - - public static async Task LoadAsync() - { - try - { - if (!File.Exists(FileFullName)) - { - await SaveAsync(); - return; - } - - await using var _ = await _lock.ReadLockAsync(); - - if (await LoadCoreAsync(FileFullName)) - return; - - Log.Information("Load backup configuration \"{FileName}\"", BackupFileFullName); - await LoadCoreAsync(BackupFileFullName); - } - catch (Exception e) - { - Log.Error(e, "Load configuration failed"); - Environment.Exit(-1); - } - } - - private static async ValueTask LoadCoreAsync(string filename) - { - try - { - Setting settings; - - await using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)) - { - settings = (await JsonSerializer.DeserializeAsync(fs, JsonSerializerOptions).ConfigureAwait(false))!; - } - - CheckSetting(settings); - Global.Settings = settings; - return true; - } - catch (Exception e) - { - Log.Error(e, "Load configuration file \"{FileName}\" error ", filename); - return false; - } - } - - private static void CheckSetting(Setting settings) - { - settings.Profiles.RemoveAll(p => p.ServerRemark == string.Empty || p.ModeRemark == string.Empty); - - if (settings.Profiles.Any(p => settings.Profiles.Any(p1 => p1 != p && p1.Index == p.Index))) - for (var i = 0; i < settings.Profiles.Count; i++) - settings.Profiles[i].Index = i; - - settings.AioDNS.ChinaDNS = DnsUtils.AppendPort(settings.AioDNS.ChinaDNS); - settings.AioDNS.OtherDNS = DnsUtils.AppendPort(settings.AioDNS.OtherDNS); - } - - /// - /// 保存配置 - /// - public static async Task SaveAsync() - { - if (_lock.IsWriteLockHeld) - return; - - try - { - await using var _ = await _lock.WriteLockAsync(); - Log.Verbose("Save Configuration"); - - if (!Directory.Exists(DataDirectoryFullName)) - Directory.CreateDirectory(DataDirectoryFullName); - - var tempFile = Path.Combine(DataDirectoryFullName, FileFullName + ".tmp"); - await using (var fileStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true)) - { - await JsonSerializer.SerializeAsync(fileStream, Global.Settings, JsonSerializerOptions); - } - - await EnsureConfigFileExistsAsync(); - - File.Replace(tempFile, FileFullName, BackupFileFullName); - } - catch (Exception e) - { - Log.Error(e, "Save Configuration error"); - } - } - - private static async ValueTask EnsureConfigFileExistsAsync() + public static async Task LoadAsync() + { + try { if (!File.Exists(FileFullName)) { - await File.Create(FileFullName).DisposeAsync(); + await SaveAsync(); + return; } + + await using var _ = await _lock.ReadLockAsync(); + + if (await LoadCoreAsync(FileFullName)) + return; + + Log.Information("Load backup configuration \"{FileName}\"", BackupFileFullName); + await LoadCoreAsync(BackupFileFullName); + } + catch (Exception e) + { + Log.Error(e, "Load configuration failed"); + Environment.Exit(-1); + } + } + + private static async ValueTask LoadCoreAsync(string filename) + { + try + { + Setting settings; + + await using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)) + { + settings = (await JsonSerializer.DeserializeAsync(fs, JsonSerializerOptions).ConfigureAwait(false))!; + } + + CheckSetting(settings); + Global.Settings = settings; + return true; + } + catch (Exception e) + { + Log.Error(e, "Load configuration file \"{FileName}\" error ", filename); + return false; + } + } + + private static void CheckSetting(Setting settings) + { + settings.Profiles.RemoveAll(p => p.ServerRemark == string.Empty || p.ModeRemark == string.Empty); + + if (settings.Profiles.Any(p => settings.Profiles.Any(p1 => p1 != p && p1.Index == p.Index))) + for (var i = 0; i < settings.Profiles.Count; i++) + settings.Profiles[i].Index = i; + + settings.AioDNS.ChinaDNS = DnsUtils.AppendPort(settings.AioDNS.ChinaDNS); + settings.AioDNS.OtherDNS = DnsUtils.AppendPort(settings.AioDNS.OtherDNS); + } + + /// + /// 保存配置 + /// + public static async Task SaveAsync() + { + if (_lock.IsWriteLockHeld) + return; + + try + { + await using var _ = await _lock.WriteLockAsync(); + Log.Verbose("Save Configuration"); + + if (!Directory.Exists(DataDirectoryFullName)) + Directory.CreateDirectory(DataDirectoryFullName); + + var tempFile = Path.Combine(DataDirectoryFullName, FileFullName + ".tmp"); + await using (var fileStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true)) + { + await JsonSerializer.SerializeAsync(fileStream, Global.Settings, JsonSerializerOptions); + } + + await EnsureConfigFileExistsAsync(); + + File.Replace(tempFile, FileFullName, BackupFileFullName); + } + catch (Exception e) + { + Log.Error(e, "Save Configuration error"); + } + } + + private static async ValueTask EnsureConfigFileExistsAsync() + { + if (!File.Exists(FileFullName)) + { + await File.Create(FileFullName).DisposeAsync(); } } } \ No newline at end of file diff --git a/Netch/Utils/DelayTestHelper.cs b/Netch/Utils/DelayTestHelper.cs index 1be4f8c5..4a24bc4b 100644 --- a/Netch/Utils/DelayTestHelper.cs +++ b/Netch/Utils/DelayTestHelper.cs @@ -1,95 +1,91 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using System.Timers; -using Microsoft.VisualStudio.Threading; +using Microsoft.VisualStudio.Threading; using Netch.Models; +using Timer = System.Timers.Timer; -namespace Netch.Utils +namespace Netch.Utils; + +public static class DelayTestHelper { - public static class DelayTestHelper + private static readonly Timer Timer; + + private static readonly AsyncSemaphore Lock = new(1); + + private static readonly AsyncSemaphore PoolLock = new(16); + + public static readonly NumberRange Range = new(0, int.MaxValue / 1000); + + private static bool _enabled = true; + + static DelayTestHelper() { - private static readonly Timer Timer; - - private static readonly AsyncSemaphore Lock = new(1); - - private static readonly AsyncSemaphore PoolLock = new(16); - - public static readonly NumberRange Range = new(0, int.MaxValue / 1000); - - private static bool _enabled = true; - - static DelayTestHelper() + Timer = new Timer { - Timer = new Timer - { - Interval = 10000, - AutoReset = true - }; + Interval = 10000, + AutoReset = true + }; - Timer.Elapsed += (_, _) => PerformTestAsync().Forget(); + Timer.Elapsed += (_, _) => PerformTestAsync().Forget(); + } + + public static bool Enabled + { + get => _enabled; + set + { + _enabled = value; + UpdateTick(); + } + } + + /// if does not get lock, block until last release + public static async Task PerformTestAsync(bool waitFinish = false) + { + if (Lock.CurrentCount == 0) + { + if (waitFinish) + (await Lock.EnterAsync()).Dispose(); + + return; } - public static bool Enabled + using var _ = await Lock.EnterAsync(); + + try { - get => _enabled; - set + var tasks = Global.Settings.Server.Select(async s => { - _enabled = value; - UpdateTick(); - } - } - - /// if does not get lock, block until last release - public static async Task PerformTestAsync(bool waitFinish = false) - { - if (Lock.CurrentCount == 0) - { - if (waitFinish) - (await Lock.EnterAsync()).Dispose(); - - return; - } - - using var _ = await Lock.EnterAsync(); - - try - { - var tasks = Global.Settings.Server.Select(async s => + using (await PoolLock.EnterAsync()) { - using (await PoolLock.EnterAsync()) - { - await s.PingAsync(); - } - }); + await s.PingAsync(); + } + }); - await Task.WhenAll(tasks); - } - catch (Exception) - { - // ignored - } + await Task.WhenAll(tasks); } - - public static void UpdateTick(bool performTestAtOnce = false) + catch (Exception) { - UpdateTick(Global.Settings.DetectionTick, performTestAtOnce); + // ignored } + } - /// interval(seconds), 0 disable, MaxValue int.MaxValue/1000 - /// - private static void UpdateTick(int interval, bool performTestAtOnce = false) + public static void UpdateTick(bool performTestAtOnce = false) + { + UpdateTick(Global.Settings.DetectionTick, performTestAtOnce); + } + + /// interval(seconds), 0 disable, MaxValue int.MaxValue/1000 + /// + private static void UpdateTick(int interval, bool performTestAtOnce = false) + { + Timer.Stop(); + + var enable = Enabled && interval > 0 && Range.InRange(interval); + if (enable) { - Timer.Stop(); - - var enable = Enabled && interval > 0 && Range.InRange(interval); - if (enable) - { - Timer.Interval = interval * 1000; - Timer.Start(); - if (performTestAtOnce) - PerformTestAsync().Forget(); - } + Timer.Interval = interval * 1000; + Timer.Start(); + if (performTestAtOnce) + PerformTestAsync().Forget(); } } } \ No newline at end of file diff --git a/Netch/Utils/DnsUtils.cs b/Netch/Utils/DnsUtils.cs index cab7e3b0..aff6a395 100644 --- a/Netch/Utils/DnsUtils.cs +++ b/Netch/Utils/DnsUtils.cs @@ -1,88 +1,83 @@ -using System; -using System.Collections; -using System.Linq; +using System.Collections; using System.Net; using System.Net.Sockets; -using System.Threading.Tasks; -using Serilog; -namespace Netch.Utils +namespace Netch.Utils; + +public static class DnsUtils { - public static class DnsUtils + /// + /// 缓存 + /// + private static readonly Hashtable Cache = new(); + private static readonly Hashtable Cache6 = new(); + + public static async Task LookupAsync(string hostname, AddressFamily inet = AddressFamily.Unspecified, int timeout = 3000) { - /// - /// 缓存 - /// - private static readonly Hashtable Cache = new(); - private static readonly Hashtable Cache6 = new(); - - public static async Task LookupAsync(string hostname, AddressFamily inet = AddressFamily.Unspecified, int timeout = 3000) + try { - try + var cacheResult = inet switch { - var cacheResult = inet switch - { - AddressFamily.Unspecified => (IPAddress?)(Cache[hostname] ?? Cache6[hostname]), - AddressFamily.InterNetwork => (IPAddress?)Cache[hostname], - AddressFamily.InterNetworkV6 => (IPAddress?)Cache6[hostname], - _ => throw new ArgumentOutOfRangeException() - }; + AddressFamily.Unspecified => (IPAddress?)(Cache[hostname] ?? Cache6[hostname]), + AddressFamily.InterNetwork => (IPAddress?)Cache[hostname], + AddressFamily.InterNetworkV6 => (IPAddress?)Cache6[hostname], + _ => throw new ArgumentOutOfRangeException() + }; - if (cacheResult != null) - return cacheResult; + if (cacheResult != null) + return cacheResult; - return await LookupNoCacheAsync(hostname, inet, timeout); - } - catch (Exception e) - { - Log.Verbose(e, "Lookup hostname {Hostname} failed", hostname); - return null; - } + return await LookupNoCacheAsync(hostname, inet, timeout); } - - private static async Task LookupNoCacheAsync(string hostname, AddressFamily inet = AddressFamily.Unspecified, int timeout = 3000) + catch (Exception e) { - using var task = Dns.GetHostAddressesAsync(hostname); - using var resTask = await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false); - - if (resTask == task) - { - var addresses = await task; - - var result = addresses.FirstOrDefault(i => inet == AddressFamily.Unspecified || inet == i.AddressFamily); - if (result == null) - return null; - - switch (result.AddressFamily) - { - case AddressFamily.InterNetwork: - Cache.Add(hostname, result); - break; - case AddressFamily.InterNetworkV6: - Cache6.Add(hostname, result); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - return result; - } - + Log.Verbose(e, "Lookup hostname {Hostname} failed", hostname); return null; } + } - public static void ClearCache() + private static async Task LookupNoCacheAsync(string hostname, AddressFamily inet = AddressFamily.Unspecified, int timeout = 3000) + { + using var task = Dns.GetHostAddressesAsync(hostname); + using var resTask = await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false); + + if (resTask == task) { - Cache.Clear(); - Cache6.Clear(); + var addresses = await task; + + var result = addresses.FirstOrDefault(i => inet == AddressFamily.Unspecified || inet == i.AddressFamily); + if (result == null) + return null; + + switch (result.AddressFamily) + { + case AddressFamily.InterNetwork: + Cache.Add(hostname, result); + break; + case AddressFamily.InterNetworkV6: + Cache6.Add(hostname, result); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return result; } - public static string AppendPort(string host, ushort port = 53) - { - if (!host.Contains(':')) - return host + $":{port}"; + return null; + } - return host; - } + public static void ClearCache() + { + Cache.Clear(); + Cache6.Clear(); + } + + public static string AppendPort(string host, ushort port = 53) + { + if (!host.Contains(':')) + return host + $":{port}"; + + return host; } } \ No newline at end of file diff --git a/Netch/Utils/Firewall.cs b/Netch/Utils/Firewall.cs index c3b3e7fe..a02b0c40 100644 --- a/Netch/Utils/Firewall.cs +++ b/Netch/Utils/Firewall.cs @@ -1,80 +1,75 @@ -using System; -using System.IO; -using System.Linq; -using Serilog; -using WindowsFirewallHelper; +using WindowsFirewallHelper; using WindowsFirewallHelper.FirewallRules; -namespace Netch.Utils +namespace Netch.Utils; + +public static class Firewall { - public static class Firewall + private const string Netch = "Netch"; + + /// + /// Netch 自带程序添加防火墙 + /// + public static void AddNetchFwRules() { - private const string Netch = "Netch"; - - /// - /// Netch 自带程序添加防火墙 - /// - public static void AddNetchFwRules() + if (!FirewallWAS.IsLocallySupported) { - if (!FirewallWAS.IsLocallySupported) - { - Log.Warning("Windows Firewall Locally Unsupported"); - return; - } - - try - { - var rule = FirewallManager.Instance.Rules.FirstOrDefault(r => r.Name == Netch); - if (rule != null) - { - if (rule.ApplicationName.StartsWith(Global.NetchDir)) - return; - - RemoveNetchFwRules(); - } - - foreach (var path in Directory.GetFiles(Global.NetchDir, "*.exe", SearchOption.AllDirectories)) - AddFwRule(Netch, path); - } - catch (Exception e) - { - Log.Warning(e, "Create Netch Firewall rules error"); - } + Log.Warning("Windows Firewall Locally Unsupported"); + return; } - /// - /// 清除防火墙规则 (Netch 自带程序) - /// - public static void RemoveNetchFwRules() + try { - if (!FirewallWAS.IsLocallySupported) - return; + var rule = FirewallManager.Instance.Rules.FirstOrDefault(r => r.Name == Netch); + if (rule != null) + { + if (rule.ApplicationName.StartsWith(Global.NetchDir)) + return; - try - { - foreach (var rule in FirewallManager.Instance.Rules.Where(r - => r.ApplicationName?.StartsWith(Global.NetchDir, StringComparison.OrdinalIgnoreCase) ?? r.Name == Netch)) - FirewallManager.Instance.Rules.Remove(rule); - } - catch (Exception e) - { - Log.Warning(e, "Remove Netch Firewall rules error"); + RemoveNetchFwRules(); } + + foreach (var path in Directory.GetFiles(Global.NetchDir, "*.exe", SearchOption.AllDirectories)) + AddFwRule(Netch, path); } - - #region 封装 - - private static void AddFwRule(string ruleName, string exeFullPath) + catch (Exception e) { - var rule = new FirewallWASRule(ruleName, - exeFullPath, - FirewallAction.Allow, - FirewallDirection.Inbound, - FirewallProfiles.Private | FirewallProfiles.Public | FirewallProfiles.Domain); - - FirewallManager.Instance.Rules.Add(rule); + Log.Warning(e, "Create Netch Firewall rules error"); } - - #endregion } + + /// + /// 清除防火墙规则 (Netch 自带程序) + /// + public static void RemoveNetchFwRules() + { + if (!FirewallWAS.IsLocallySupported) + return; + + try + { + foreach (var rule in FirewallManager.Instance.Rules.Where(r + => r.ApplicationName?.StartsWith(Global.NetchDir, StringComparison.OrdinalIgnoreCase) ?? r.Name == Netch)) + FirewallManager.Instance.Rules.Remove(rule); + } + catch (Exception e) + { + Log.Warning(e, "Remove Netch Firewall rules error"); + } + } + + #region 封装 + + private static void AddFwRule(string ruleName, string exeFullPath) + { + var rule = new FirewallWASRule(ruleName, + exeFullPath, + FirewallAction.Allow, + FirewallDirection.Inbound, + FirewallProfiles.Private | FirewallProfiles.Public | FirewallProfiles.Domain); + + FirewallManager.Instance.Rules.Add(rule); + } + + #endregion } \ No newline at end of file diff --git a/Netch/Utils/ModeHelper.cs b/Netch/Utils/ModeHelper.cs index 4bb7e87a..341a4cf8 100644 --- a/Netch/Utils/ModeHelper.cs +++ b/Netch/Utils/ModeHelper.cs @@ -1,6 +1,3 @@ -using System; -using System.IO; -using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using Netch.JsonConverter; @@ -10,133 +7,132 @@ using Netch.Models.Modes.ShareMode; using Netch.Models.Modes.TunMode; using Netch.Services; -namespace Netch.Utils +namespace Netch.Utils; + +public static class ModeHelper { - public static class ModeHelper + private static readonly JsonSerializerOptions JsonSerializerOptions = Global.NewCustomJsonSerializerOptions(); + + static ModeHelper() { - private static readonly JsonSerializerOptions JsonSerializerOptions = Global.NewCustomJsonSerializerOptions(); + JsonSerializerOptions.Converters.Add(new ModeConverterWithTypeDiscriminator()); + JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.Never; + } - static ModeHelper() + public static Mode LoadMode(string file) + { + if (file.EndsWith(".json")) + return LoadJsonMode(file); + + if (file.EndsWith(".txt")) + return ReadTxtMode(file); + + throw new NotSupportedException(); + } + + private static Mode LoadJsonMode(string file) + { + using var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true); + var mode = JsonSerializer.Deserialize(fs, JsonSerializerOptions) ?? throw new ArgumentNullException(); + mode.FullName = file; + return mode; + } + + public static void WriteFile(this Mode mode) + { + using var fs = new FileStream(mode.FullName, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true); + JsonSerializer.Serialize(fs, mode, JsonSerializerOptions); + } + + private static Mode ReadTxtMode(string file) + { + Mode mode; + var ls = File.ReadAllLines(file); + string modeTypeNum; + + if (ls.First().First() != '#') + throw new FormatException("Not a valid txt mode that begins with meta line"); + + var heads = ls[0][1..].Split(",", StringSplitOptions.TrimEntries); + switch (modeTypeNum = heads.ElementAtOrDefault(1) ?? "0") { - JsonSerializerOptions.Converters.Add(new ModeConverterWithTypeDiscriminator()); - JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); - JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.Never; + case "0": + mode = new Redirector { FullName = file }; + break; + case "1": + case "2": + mode = new TunMode { FullName = file }; + break; + case "6": + mode = new ShareMode { FullName = file }; + break; + default: + throw new ArgumentOutOfRangeException(); } - public static Mode LoadMode(string file) + mode.Remark.Add("en", heads[0]); + + foreach (var l in ls.Skip(1)) { - if (file.EndsWith(".json")) - return LoadJsonMode(file); + if (l.IsNullOrWhiteSpace()) + continue; - if (file.EndsWith(".txt")) - return ReadTxtMode(file); + if (l.StartsWith("//")) + continue; - throw new NotSupportedException(); - } - - private static Mode LoadJsonMode(string file) - { - using var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true); - var mode = JsonSerializer.Deserialize(fs, JsonSerializerOptions) ?? throw new ArgumentNullException(); - mode.FullName = file; - return mode; - } - - public static void WriteFile(this Mode mode) - { - using var fs = new FileStream(mode.FullName, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true); - JsonSerializer.Serialize(fs, mode, JsonSerializerOptions); - } - - private static Mode ReadTxtMode(string file) - { - Mode mode; - var ls = File.ReadAllLines(file); - string modeTypeNum; - - if (ls.First().First() != '#') - throw new FormatException("Not a valid txt mode that begins with meta line"); - - var heads = ls[0][1..].Split(",", StringSplitOptions.TrimEntries); - switch (modeTypeNum = heads.ElementAtOrDefault(1) ?? "0") + Mode? includeMode = null; + if (l.StartsWith("#include")) { - case "0": - mode = new Redirector { FullName = file }; + var relativePath = l["#include ".Length..].Replace("<", "").Replace(">", "").Replace(".h", ".txt").Trim(); + includeMode = ReadTxtMode(ModeService.Instance.GetFullPath(relativePath)); + } + + switch (mode) + { + case Redirector processMode: + if (includeMode is Redirector pMode) + { + processMode.Bypass.AddRange(pMode.Bypass); + processMode.Handle.AddRange(pMode.Handle); + } + else if (l.StartsWith("!")) + processMode.Bypass.Add(l); + else + processMode.Handle.Add(l); + break; - case "1": - case "2": - mode = new TunMode { FullName = file }; + case ShareMode shareMode: + shareMode.Argument = l; break; - case "6": - mode = new ShareMode { FullName = file }; + case TunMode tunMode: + if (includeMode is TunMode tMode) + { + tunMode.Bypass.AddRange(tMode.Bypass); + tMode.Handle.AddRange(tMode.Handle); + break; + } + + switch (modeTypeNum) + { + case "1": + tunMode.Handle.Add(l); + break; + case "2": + tunMode.Bypass.Add(l); + break; + } + break; default: throw new ArgumentOutOfRangeException(); } - - mode.Remark.Add("en", heads[0]); - - foreach (var l in ls.Skip(1)) - { - if (l.IsNullOrWhiteSpace()) - continue; - - if (l.StartsWith("//")) - continue; - - Mode? includeMode = null; - if (l.StartsWith("#include")) - { - var relativePath = l["#include ".Length..].Replace("<", "").Replace(">", "").Replace(".h", ".txt").Trim(); - includeMode = ReadTxtMode(ModeService.Instance.GetFullPath(relativePath)); - } - - switch (mode) - { - case Redirector processMode: - if (includeMode is Redirector pMode) - { - processMode.Bypass.AddRange(pMode.Bypass); - processMode.Handle.AddRange(pMode.Handle); - } - else if (l.StartsWith("!")) - processMode.Bypass.Add(l); - else - processMode.Handle.Add(l); - - break; - case ShareMode shareMode: - shareMode.Argument = l; - break; - case TunMode tunMode: - if (includeMode is TunMode tMode) - { - tunMode.Bypass.AddRange(tMode.Bypass); - tMode.Handle.AddRange(tMode.Handle); - break; - } - - switch (modeTypeNum) - { - case "1": - tunMode.Handle.Add(l); - break; - case "2": - tunMode.Bypass.Add(l); - break; - } - - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - if (modeTypeNum == "2") - ((TunMode)mode).Handle.Add("0.0.0.0/0"); - - return mode; } + + if (modeTypeNum == "2") + ((TunMode)mode).Handle.Add("0.0.0.0/0"); + + return mode; } } \ No newline at end of file diff --git a/Netch/Utils/NetworkInterfaceUtils.cs b/Netch/Utils/NetworkInterfaceUtils.cs index 2e79e8d7..102f3ef1 100644 --- a/Netch/Utils/NetworkInterfaceUtils.cs +++ b/Netch/Utils/NetworkInterfaceUtils.cs @@ -1,6 +1,4 @@ -using System; -using System.Diagnostics; -using System.Linq; +using System.Diagnostics; using System.Management; using System.Net; using System.Net.NetworkInformation; @@ -8,94 +6,93 @@ using System.Net.Sockets; using Windows.Win32; using Netch.Models; -namespace Netch.Utils +namespace Netch.Utils; + +public static class NetworkInterfaceUtils { - public static class NetworkInterfaceUtils + public static NetworkInterface GetBest(AddressFamily addressFamily = AddressFamily.InterNetwork) { - public static NetworkInterface GetBest(AddressFamily addressFamily = AddressFamily.InterNetwork) + string ipAddress; + switch (addressFamily) { - string ipAddress; - switch (addressFamily) - { - case AddressFamily.InterNetwork: - ipAddress = "114.114.114.114"; - break; - case AddressFamily.InterNetworkV6: - throw new NotImplementedException(); - default: - throw new InvalidOperationException(); - } - - if (PInvoke.GetBestRoute(BitConverter.ToUInt32(IPAddress.Parse(ipAddress).GetAddressBytes(), 0), 0, out var route) != 0) - throw new MessageException("GetBestRoute 搜索失败"); - - return Get((int)route.dwForwardIfIndex); + case AddressFamily.InterNetwork: + ipAddress = "114.114.114.114"; + break; + case AddressFamily.InterNetworkV6: + throw new NotImplementedException(); + default: + throw new InvalidOperationException(); } - public static NetworkInterface Get(int interfaceIndex) - { - return NetworkInterface.GetAllNetworkInterfaces().First(n => n.GetIndex() == interfaceIndex); - } + if (PInvoke.GetBestRoute(BitConverter.ToUInt32(IPAddress.Parse(ipAddress).GetAddressBytes(), 0), 0, out var route) != 0) + throw new MessageException("GetBestRoute 搜索失败"); - public static NetworkInterface Get(Func expression) - { - return NetworkInterface.GetAllNetworkInterfaces().First(expression); - } - - public static void SetInterfaceMetric(int interfaceIndex, int? metric = null) - { - var arguments = $"interface ip set interface {interfaceIndex} "; - if (metric != null) - arguments += $"metric={metric} "; - - Process.Start(new ProcessStartInfo("netsh.exe", arguments) - { - UseShellExecute = false, - Verb = "runas" - })!.WaitForExit(); - } + return Get((int)route.dwForwardIfIndex); } - public static class NetworkInterfaceExtension + public static NetworkInterface Get(int interfaceIndex) { - public static int GetIndex(this NetworkInterface ni) + return NetworkInterface.GetAllNetworkInterfaces().First(n => n.GetIndex() == interfaceIndex); + } + + public static NetworkInterface Get(Func expression) + { + return NetworkInterface.GetAllNetworkInterfaces().First(expression); + } + + public static void SetInterfaceMetric(int interfaceIndex, int? metric = null) + { + var arguments = $"interface ip set interface {interfaceIndex} "; + if (metric != null) + arguments += $"metric={metric} "; + + Process.Start(new ProcessStartInfo("netsh.exe", arguments) { - var ipProperties = ni.GetIPProperties(); - if (ni.Supports(NetworkInterfaceComponent.IPv4)) - return ipProperties.GetIPv4Properties().Index; + UseShellExecute = false, + Verb = "runas" + })!.WaitForExit(); + } +} - if (ni.Supports(NetworkInterfaceComponent.IPv6)) - return ipProperties.GetIPv6Properties().Index; +public static class NetworkInterfaceExtension +{ + public static int GetIndex(this NetworkInterface ni) + { + var ipProperties = ni.GetIPProperties(); + if (ni.Supports(NetworkInterfaceComponent.IPv4)) + return ipProperties.GetIPv4Properties().Index; - throw new Exception(); + if (ni.Supports(NetworkInterfaceComponent.IPv6)) + return ipProperties.GetIPv6Properties().Index; + + throw new Exception(); + } + + public static void SetDns(this NetworkInterface ni, string primaryDns, string? secondDns = null) + { + void VerifyDns(ref string s) + { + s = s.Trim(); + if (primaryDns.IsNullOrEmpty()) + throw new ArgumentException("DNS format invalid", nameof(primaryDns)); } - public static void SetDns(this NetworkInterface ni, string primaryDns, string? secondDns = null) - { - void VerifyDns(ref string s) - { - s = s.Trim(); - if (primaryDns.IsNullOrEmpty()) - throw new ArgumentException("DNS format invalid", nameof(primaryDns)); - } - + VerifyDns(ref primaryDns); + if (secondDns != null) VerifyDns(ref primaryDns); - if (secondDns != null) - VerifyDns(ref primaryDns); - var wmi = new ManagementClass("Win32_NetworkAdapterConfiguration"); - var mos = wmi.GetInstances().Cast(); + var wmi = new ManagementClass("Win32_NetworkAdapterConfiguration"); + var mos = wmi.GetInstances().Cast(); - var mo = mos.First(m => m["Description"].ToString() == ni.Description); + var mo = mos.First(m => m["Description"].ToString() == ni.Description); - var dns = new[] { primaryDns }; - if (secondDns != null) - dns = dns.Append(secondDns).ToArray(); + var dns = new[] { primaryDns }; + if (secondDns != null) + dns = dns.Append(secondDns).ToArray(); - var inPar = mo.GetMethodParameters("SetDNSServerSearchOrder"); - inPar["DNSServerSearchOrder"] = dns; + var inPar = mo.GetMethodParameters("SetDNSServerSearchOrder"); + inPar["DNSServerSearchOrder"] = dns; - mo.InvokeMethod("SetDNSServerSearchOrder", inPar, null); - } + mo.InvokeMethod("SetDNSServerSearchOrder", inPar, null); } } \ No newline at end of file diff --git a/Netch/Utils/PortHelper.cs b/Netch/Utils/PortHelper.cs index 1a51abde..a77f2cf0 100644 --- a/Netch/Utils/PortHelper.cs +++ b/Netch/Utils/PortHelper.cs @@ -1,231 +1,226 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Diagnostics; -using System.Linq; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Runtime.InteropServices; using Windows.Win32; using Windows.Win32.NetworkManagement.IpHelper; using Netch.Models; -using Serilog; -namespace Netch.Utils +namespace Netch.Utils; + +public static class PortHelper { - public static class PortHelper + private static readonly List TCPReservedRanges = new(); + private static readonly List UDPReservedRanges = new(); + private static readonly IPGlobalProperties NetInfo = IPGlobalProperties.GetIPGlobalProperties(); + + static PortHelper() { - private static readonly List TCPReservedRanges = new(); - private static readonly List UDPReservedRanges = new(); - private static readonly IPGlobalProperties NetInfo = IPGlobalProperties.GetIPGlobalProperties(); - - static PortHelper() + try { - try - { - GetReservedPortRange(PortType.TCP, ref TCPReservedRanges); - GetReservedPortRange(PortType.UDP, ref UDPReservedRanges); - } - catch (Exception e) - { - Log.Error(e, "Get reserved ports failed"); - } + GetReservedPortRange(PortType.TCP, ref TCPReservedRanges); + GetReservedPortRange(PortType.UDP, ref UDPReservedRanges); } - - internal static IEnumerable GetProcessByUsedTcpPort(ushort port, AddressFamily inet = AddressFamily.InterNetwork) + catch (Exception e) { - if (port == 0) - throw new ArgumentOutOfRangeException(); + Log.Error(e, "Get reserved ports failed"); + } + } - switch (inet) + internal static IEnumerable GetProcessByUsedTcpPort(ushort port, AddressFamily inet = AddressFamily.InterNetwork) + { + if (port == 0) + throw new ArgumentOutOfRangeException(); + + switch (inet) + { + case AddressFamily.InterNetwork: { - case AddressFamily.InterNetwork: + var process = new List(); + unsafe { - var process = new List(); - unsafe + uint err; + uint size = 0; + PInvoke.GetExtendedTcpTable(default, ref size, false, (uint)inet, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_LISTENER, 0); // get size + var tcpTable = (MIB_TCPTABLE_OWNER_PID*)Marshal.AllocHGlobal((int)size); + + if ((err = PInvoke.GetExtendedTcpTable(tcpTable, ref size, false, (uint)inet, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_LISTENER, 0)) != + 0) + throw new Win32Exception((int)err); + + for (var i = 0; i < tcpTable -> dwNumEntries; i++) { - uint err; - uint size = 0; - PInvoke.GetExtendedTcpTable(default, ref size, false, (uint)inet, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_LISTENER, 0); // get size - var tcpTable = (MIB_TCPTABLE_OWNER_PID*)Marshal.AllocHGlobal((int)size); + var row = tcpTable -> table.ReadOnlyItemRef(i); - if ((err = PInvoke.GetExtendedTcpTable(tcpTable, ref size, false, (uint)inet, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_LISTENER, 0)) != - 0) - throw new Win32Exception((int)err); + if (row.dwOwningPid is 0 or 4) + continue; - for (var i = 0; i < tcpTable -> dwNumEntries; i++) - { - var row = tcpTable -> table.ReadOnlyItemRef(i); - - if (row.dwOwningPid is 0 or 4) - continue; - - if (PInvoke.ntohs((ushort)row.dwLocalPort) == port) - process.Add(Process.GetProcessById((int)row.dwOwningPid)); - } + if (PInvoke.ntohs((ushort)row.dwLocalPort) == port) + process.Add(Process.GetProcessById((int)row.dwOwningPid)); } - - return process; } - case AddressFamily.InterNetworkV6: - throw new NotImplementedException(); - default: - throw new InvalidOperationException(); + + return process; } + case AddressFamily.InterNetworkV6: + throw new NotImplementedException(); + default: + throw new InvalidOperationException(); } + } - private static void GetReservedPortRange(PortType portType, ref List targetList) + private static void GetReservedPortRange(PortType portType, ref List targetList) + { + var process = new Process { - var process = new Process + StartInfo = new ProcessStartInfo { - StartInfo = new ProcessStartInfo - { - FileName = "netsh", - Arguments = $" int ipv4 show excludedportrange {portType}", - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true - } - }; - - process.Start(); - var output = process.StandardOutput.ReadToEnd(); - - foreach (var line in output.SplitRemoveEmptyEntriesAndTrimEntries('\n')) - { - var value = line.Trim().SplitRemoveEmptyEntries(' '); - if (value.Length < 2) - continue; - - if (!ushort.TryParse(value[0], out var start) || !ushort.TryParse(value[1], out var end)) - continue; - - targetList.Add(new NumberRange(start, end)); + FileName = "netsh", + Arguments = $" int ipv4 show excludedportrange {portType}", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true } - } + }; - /// - /// 指定类型的端口是否已经被使用了 - /// - /// 端口 - /// 检查端口类型 - /// 是否被占用 - public static void CheckPort(ushort port, PortType type = PortType.Both) + process.Start(); + var output = process.StandardOutput.ReadToEnd(); + + foreach (var line in output.SplitRemoveEmptyEntriesAndTrimEntries('\n')) { - switch (type) - { - case PortType.Both: - CheckPort(port, PortType.TCP); - CheckPort(port, PortType.UDP); - break; - default: - CheckPortInUse(port, type); - CheckPortReserved(port, type); - break; - } - } + var value = line.Trim().SplitRemoveEmptyEntries(' '); + if (value.Length < 2) + continue; - private static void CheckPortInUse(ushort port, PortType type) - { - switch (type) - { - case PortType.Both: - CheckPortInUse(port, PortType.TCP); - CheckPortInUse(port, PortType.UDP); - break; - case PortType.TCP: - if (NetInfo.GetActiveTcpListeners().Any(ipEndPoint => ipEndPoint.Port == port)) - throw new PortInUseException(); + if (!ushort.TryParse(value[0], out var start) || !ushort.TryParse(value[1], out var end)) + continue; - break; - case PortType.UDP: - if (NetInfo.GetActiveUdpListeners().Any(ipEndPoint => ipEndPoint.Port == port)) - throw new PortInUseException(); - - break; - default: - throw new ArgumentOutOfRangeException(nameof(type), type, null); - } - } - - /// - /// 检查端口是否是保留端口 - /// - private static void CheckPortReserved(ushort port, PortType type) - { - switch (type) - { - case PortType.Both: - CheckPortReserved(port, PortType.TCP); - CheckPortReserved(port, PortType.UDP); - return; - case PortType.TCP: - if (TCPReservedRanges.Any(range => range.InRange(port))) - throw new PortReservedException(); - - break; - case PortType.UDP: - if (UDPReservedRanges.Any(range => range.InRange(port))) - throw new PortReservedException(); - - break; - default: - Trace.Assert(false); - return; - } - } - - public static ushort GetAvailablePort(PortType portType = PortType.Both) - { - var random = new Random(); - for (ushort i = 0; i < 55535; i++) - { - var p = (ushort)random.Next(10000, 65535); - try - { - CheckPort(p, portType); - return p; - } - catch (Exception) - { - // ignored - } - } - - throw new Exception(); + targetList.Add(new NumberRange(start, end)); } } /// - /// 检查端口类型 + /// 指定类型的端口是否已经被使用了 /// - [Flags] - public enum PortType + /// 端口 + /// 检查端口类型 + /// 是否被占用 + public static void CheckPort(ushort port, PortType type = PortType.Both) { - TCP = 0b_01, - UDP = 0b_10, - Both = TCP | UDP - } - - public class PortInUseException : Exception - { - public PortInUseException(string message) : base(message) - { - } - - public PortInUseException() + switch (type) { + case PortType.Both: + CheckPort(port, PortType.TCP); + CheckPort(port, PortType.UDP); + break; + default: + CheckPortInUse(port, type); + CheckPortReserved(port, type); + break; } } - public class PortReservedException : Exception + private static void CheckPortInUse(ushort port, PortType type) { - public PortReservedException(string message) : base(message) + switch (type) { + case PortType.Both: + CheckPortInUse(port, PortType.TCP); + CheckPortInUse(port, PortType.UDP); + break; + case PortType.TCP: + if (NetInfo.GetActiveTcpListeners().Any(ipEndPoint => ipEndPoint.Port == port)) + throw new PortInUseException(); + + break; + case PortType.UDP: + if (NetInfo.GetActiveUdpListeners().Any(ipEndPoint => ipEndPoint.Port == port)) + throw new PortInUseException(); + + break; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + } + + /// + /// 检查端口是否是保留端口 + /// + private static void CheckPortReserved(ushort port, PortType type) + { + switch (type) + { + case PortType.Both: + CheckPortReserved(port, PortType.TCP); + CheckPortReserved(port, PortType.UDP); + return; + case PortType.TCP: + if (TCPReservedRanges.Any(range => range.InRange(port))) + throw new PortReservedException(); + + break; + case PortType.UDP: + if (UDPReservedRanges.Any(range => range.InRange(port))) + throw new PortReservedException(); + + break; + default: + Trace.Assert(false); + return; + } + } + + public static ushort GetAvailablePort(PortType portType = PortType.Both) + { + var random = new Random(); + for (ushort i = 0; i < 55535; i++) + { + var p = (ushort)random.Next(10000, 65535); + try + { + CheckPort(p, portType); + return p; + } + catch (Exception) + { + // ignored + } } - public PortReservedException() - { - } + throw new Exception(); + } +} + +/// +/// 检查端口类型 +/// +[Flags] +public enum PortType +{ + TCP = 0b_01, + UDP = 0b_10, + Both = TCP | UDP +} + +public class PortInUseException : Exception +{ + public PortInUseException(string message) : base(message) + { + } + + public PortInUseException() + { + } +} + +public class PortReservedException : Exception +{ + public PortReservedException(string message) : base(message) + { + } + + public PortReservedException() + { } } \ No newline at end of file diff --git a/Netch/Utils/RouteUtils.cs b/Netch/Utils/RouteUtils.cs index 26260242..e24a9953 100644 --- a/Netch/Utils/RouteUtils.cs +++ b/Netch/Utils/RouteUtils.cs @@ -1,86 +1,83 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.Net.Sockets; using Netch.Interops; using Netch.Models; -using Serilog; -namespace Netch.Utils +namespace Netch.Utils; + +public static class RouteUtils { - public static class RouteUtils + public static void CreateRouteFill(NetRoute template, IEnumerable rules, int? metric = null) { - public static void CreateRouteFill(NetRoute template, IEnumerable rules, int? metric = null) + foreach (var rule in rules) + CreateRouteFill(template, rule, metric); + } + + public static bool CreateRouteFill(NetRoute template, string rule, int? metric = null) + { + if (!TryParseIPNetwork(rule, out var network, out var cidr)) { - foreach (var rule in rules) - CreateRouteFill(template, rule, metric); + Log.Warning("invalid rule {Rule}", rule); + return false; } - public static bool CreateRouteFill(NetRoute template, string rule, int? metric = null) - { - if (!TryParseIPNetwork(rule, out var network, out var cidr)) - { - Log.Warning("invalid rule {Rule}", rule); - return false; - } + return CreateRoute(template.FillTemplate(network, (byte)cidr, metric)); + } - return CreateRoute(template.FillTemplate(network, (byte)cidr, metric)); + public static bool CreateRoute(NetRoute o) + { + Log.Verbose("CreateRoute {InterNetwork} {Address} {Cidr} {Gateway} {Interface} {Metric}", + AddressFamily.InterNetwork, + o.Network, + o.Cidr, + o.Gateway, + (ulong)o.InterfaceIndex, + o.Metric); + + return RouteHelper.CreateRoute(AddressFamily.InterNetwork, o.Network, o.Cidr, o.Gateway, (ulong)o.InterfaceIndex, o.Metric); + } + + public static void DeleteRouteFill(NetRoute template, IEnumerable rules, int? metric = null) + { + foreach (var rule in rules) + DeleteRouteFill(template, rule, metric); + } + + public static bool DeleteRouteFill(NetRoute template, string rule, int? metric = null) + { + if (!TryParseIPNetwork(rule, out var network, out var cidr)) + { + Log.Warning("invalid rule {Rule}", rule); + return false; } - public static bool CreateRoute(NetRoute o) - { - Log.Verbose("CreateRoute {InterNetwork} {Address} {Cidr} {Gateway} {Interface} {Metric}", - AddressFamily.InterNetwork, - o.Network, - o.Cidr, - o.Gateway, - (ulong)o.InterfaceIndex, - o.Metric); + return DeleteRoute(template.FillTemplate(network, (byte)cidr, metric)); + } - return RouteHelper.CreateRoute(AddressFamily.InterNetwork, o.Network, o.Cidr, o.Gateway, (ulong)o.InterfaceIndex, o.Metric); - } + public static bool DeleteRoute(NetRoute o) + { + Log.Verbose("DeleteRoute {InterNetwork} {Address} {Cidr} {Gateway} {Interface} {Metric}", + AddressFamily.InterNetwork, + o.Network, + o.Cidr, + o.Gateway, + (ulong)o.InterfaceIndex, + o.Metric); - public static void DeleteRouteFill(NetRoute template, IEnumerable rules, int? metric = null) - { - foreach (var rule in rules) - DeleteRouteFill(template, rule, metric); - } + return RouteHelper.DeleteRoute(AddressFamily.InterNetwork, o.Network, o.Cidr, o.Gateway, (ulong)o.InterfaceIndex, o.Metric); + } - public static bool DeleteRouteFill(NetRoute template, string rule, int? metric = null) - { - if (!TryParseIPNetwork(rule, out var network, out var cidr)) - { - Log.Warning("invalid rule {Rule}", rule); - return false; - } + public static bool TryParseIPNetwork(string ipNetwork, [NotNullWhen(true)] out string? ip, out int cidr) + { + ip = null; + cidr = 0; - return DeleteRoute(template.FillTemplate(network, (byte)cidr, metric)); - } + var s = ipNetwork.Split('/'); + if (s.Length != 2) + return false; - public static bool DeleteRoute(NetRoute o) - { - Log.Verbose("DeleteRoute {InterNetwork} {Address} {Cidr} {Gateway} {Interface} {Metric}", - AddressFamily.InterNetwork, - o.Network, - o.Cidr, - o.Gateway, - (ulong)o.InterfaceIndex, - o.Metric); - - return RouteHelper.DeleteRoute(AddressFamily.InterNetwork, o.Network, o.Cidr, o.Gateway, (ulong)o.InterfaceIndex, o.Metric); - } - - public static bool TryParseIPNetwork(string ipNetwork, [NotNullWhen(true)] out string? ip, out int cidr) - { - ip = null; - cidr = 0; - - var s = ipNetwork.Split('/'); - if (s.Length != 2) - return false; - - ip = s[0]; - cidr = int.Parse(s[1]); - return true; - } + ip = s[0]; + cidr = int.Parse(s[1]); + return true; } } \ No newline at end of file diff --git a/Netch/Utils/ServerHelper.cs b/Netch/Utils/ServerHelper.cs index 35ce4c4d..1c506ee7 100644 --- a/Netch/Utils/ServerHelper.cs +++ b/Netch/Utils/ServerHelper.cs @@ -1,37 +1,33 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using Netch.Interfaces; -namespace Netch.Utils +namespace Netch.Utils; + +public static class ServerHelper { - public static class ServerHelper + static ServerHelper() { - static ServerHelper() - { - var serversUtilsTypes = Assembly.GetExecutingAssembly() - .GetExportedTypes() - .Where(type => type.GetInterfaces().Contains(typeof(IServerUtil))); + var serversUtilsTypes = Assembly.GetExecutingAssembly() + .GetExportedTypes() + .Where(type => type.GetInterfaces().Contains(typeof(IServerUtil))); - ServerUtilDictionary = serversUtilsTypes.Select(t => (IServerUtil)Activator.CreateInstance(t)!).ToDictionary(util => util.TypeName); - } + ServerUtilDictionary = serversUtilsTypes.Select(t => (IServerUtil)Activator.CreateInstance(t)!).ToDictionary(util => util.TypeName); + } - public static Dictionary ServerUtilDictionary { get; } + public static Dictionary ServerUtilDictionary { get; } - public static IServerUtil GetUtilByTypeName(string typeName) - { - return ServerUtilDictionary.GetValueOrDefault(typeName) ?? throw new NotSupportedException("Specified server type is not supported."); - } + public static IServerUtil GetUtilByTypeName(string typeName) + { + return ServerUtilDictionary.GetValueOrDefault(typeName) ?? throw new NotSupportedException("Specified server type is not supported."); + } - public static IServerUtil? GetUtilByUriScheme(string scheme) - { - return ServerUtilDictionary.Values.SingleOrDefault(i => i.UriScheme.Any(s => s.Equals(scheme))); - } + public static IServerUtil? GetUtilByUriScheme(string scheme) + { + return ServerUtilDictionary.Values.SingleOrDefault(i => i.UriScheme.Any(s => s.Equals(scheme))); + } - public static Type GetTypeByTypeName(string typeName) - { - return GetUtilByTypeName(typeName).ServerType; - } + public static Type GetTypeByTypeName(string typeName) + { + return GetUtilByTypeName(typeName).ServerType; } } \ No newline at end of file diff --git a/Netch/Utils/ShareLink.cs b/Netch/Utils/ShareLink.cs index 71b7ad85..6d2a9531 100644 --- a/Netch/Utils/ShareLink.cs +++ b/Netch/Utils/ShareLink.cs @@ -1,215 +1,209 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; +using System.Text; using System.Text.Json; using Netch.JsonConverter; using Netch.Models; using Netch.Servers; -using Serilog; -namespace Netch.Utils +namespace Netch.Utils; + +public static class ShareLink { - public static class ShareLink + public static string GetShareLink(Server server) { - public static string GetShareLink(Server server) - { - return ServerHelper.GetUtilByTypeName(server.Type).GetShareLink(server); - } - - public static List ParseText(string text) - { - try - { - text = URLSafeBase64Decode(text); - } - catch - { - // ignored - } - - var list = new List(); - - try - { - list.AddRange(JsonSerializer.Deserialize>(text)!.Select(server => new ShadowsocksServer - { - Hostname = server.server, - Port = server.server_port, - EncryptMethod = server.method, - Password = server.password, - Remark = server.remarks, - Plugin = server.plugin, - PluginOption = server.plugin_opts - })); - } - catch (JsonException) - { - foreach (var line in text.GetLines()) - { - try - { - list.AddRange(ParseUri(line)); - } - catch (Exception e) - { - Log.Error(e, "Parse servers from share link error"); - } - } - } - catch (Exception e) - { - Log.Error(e, "Parse servers from share link error"); - } - - return list; - } - - private static IEnumerable ParseUri(in string text) - { - var list = new List(); - - if (text.StartsWith("tg://socks?") || text.StartsWith("https://t.me/socks?")) - { - list.AddRange(ServerHelper.GetUtilByTypeName("Socks5").ParseUri(text)); - } - else if (text.StartsWith("Netch://")) - { - list.Add(ParseNetchUri(text)); - } - else - { - var scheme = GetUriScheme(text); - var util = ServerHelper.GetUtilByUriScheme(scheme); - if (util != null) - list.AddRange(util.ParseUri(text)); - else - Log.Warning("\"{Scheme}\" scheme share link not supported", scheme); - } - - foreach (var node in list.Where(node => !node.Remark.IsNullOrWhiteSpace())) - node.Remark = RemoveEmoji(node.Remark); - - return list; - } - - public static string GetUriScheme(string text) - { - var endIndex = text.IndexOf("://", StringComparison.Ordinal); - if (endIndex == -1) - throw new UriFormatException("Text is not a URI"); - - return text.Substring(0, endIndex); - } - - private static Server ParseNetchUri(string text) - { - text = URLSafeBase64Decode(text.Substring(8)); - - var NetchLink = JsonSerializer.Deserialize(text); - - if (string.IsNullOrEmpty(NetchLink.GetProperty("Hostname").GetString())) - throw new FormatException(); - - if (!ushort.TryParse(NetchLink.GetProperty("Port").GetString(), out _)) - throw new FormatException(); - - return JsonSerializer.Deserialize(text, - new JsonSerializerOptions - { - Converters = { new ServerConverterWithTypeDiscriminator() } - })!; - } - - public static string GetNetchLink(Server s) - { - var jsonSerializerOptions = Global.NewCustomJsonSerializerOptions(); - jsonSerializerOptions.WriteIndented = false; - return "Netch://" + URLSafeBase64Encode(JsonSerializer.Serialize(s, jsonSerializerOptions)); - } - - #region Utils - - /// - /// URL 传输安全的 Base64 解码 - /// - /// 需要解码的字符串 - /// 解码后的字符串 - public static string URLSafeBase64Decode(string text) - { - return Encoding.UTF8.GetString( - Convert.FromBase64String(text.Replace("-", "+").Replace("_", "/").PadRight(text.Length + (4 - text.Length % 4) % 4, '='))); - } - - /// - /// URL 传输安全的 Base64 加密 - /// - /// 需要加密的字符串 - /// 加密后的字符串 - public static string URLSafeBase64Encode(string text) - { - return Convert.ToBase64String(Encoding.UTF8.GetBytes(text)).Replace("+", "-").Replace("/", "_").Replace("=", ""); - } - - private static string RemoveEmoji(string text) - { - byte[] emojiBytes = { 240, 159 }; - var remark = Encoding.UTF8.GetBytes(text); - var startIndex = 0; - while (remark.Length > startIndex + 1 && remark[startIndex] == emojiBytes[0] && remark[startIndex + 1] == emojiBytes[1]) - startIndex += 4; - - return Encoding.UTF8.GetString(remark.Skip(startIndex).ToArray()).Trim(); - } - - public static string UnBase64String(string value) - { - if (string.IsNullOrEmpty(value)) - return ""; - - var bytes = Convert.FromBase64String(value); - return Encoding.UTF8.GetString(bytes); - } - - public static string ToBase64String(string value) - { - if (string.IsNullOrEmpty(value)) - return ""; - - var bytes = Encoding.UTF8.GetBytes(value); - return Convert.ToBase64String(bytes); - } - - public static Dictionary ParseParam(string paramStr) - { - var paramsDict = new Dictionary(); - var obfsParams = paramStr.Split('&'); - foreach (var p in obfsParams) - if (p.IndexOf('=') > 0) - { - var index = p.IndexOf('='); - var key = p.Substring(0, index); - var val = p.Substring(index + 1); - paramsDict[key] = val; - } - - return paramsDict; - } - - public static IEnumerable GetLines(this string str, bool removeEmptyLines = true) - { - using var sr = new StringReader(str); - string? line; - while ((line = sr.ReadLine()) != null) - { - if (removeEmptyLines && string.IsNullOrWhiteSpace(line)) - continue; - - yield return line; - } - } - - #endregion + return ServerHelper.GetUtilByTypeName(server.Type).GetShareLink(server); } + + public static List ParseText(string text) + { + try + { + text = URLSafeBase64Decode(text); + } + catch + { + // ignored + } + + var list = new List(); + + try + { + list.AddRange(JsonSerializer.Deserialize>(text)!.Select(server => new ShadowsocksServer + { + Hostname = server.server, + Port = server.server_port, + EncryptMethod = server.method, + Password = server.password, + Remark = server.remarks, + Plugin = server.plugin, + PluginOption = server.plugin_opts + })); + } + catch (JsonException) + { + foreach (var line in text.GetLines()) + { + try + { + list.AddRange(ParseUri(line)); + } + catch (Exception e) + { + Log.Error(e, "Parse servers from share link error"); + } + } + } + catch (Exception e) + { + Log.Error(e, "Parse servers from share link error"); + } + + return list; + } + + private static IEnumerable ParseUri(in string text) + { + var list = new List(); + + if (text.StartsWith("tg://socks?") || text.StartsWith("https://t.me/socks?")) + { + list.AddRange(ServerHelper.GetUtilByTypeName("Socks5").ParseUri(text)); + } + else if (text.StartsWith("Netch://")) + { + list.Add(ParseNetchUri(text)); + } + else + { + var scheme = GetUriScheme(text); + var util = ServerHelper.GetUtilByUriScheme(scheme); + if (util != null) + list.AddRange(util.ParseUri(text)); + else + Log.Warning("\"{Scheme}\" scheme share link not supported", scheme); + } + + foreach (var node in list.Where(node => !node.Remark.IsNullOrWhiteSpace())) + node.Remark = RemoveEmoji(node.Remark); + + return list; + } + + public static string GetUriScheme(string text) + { + var endIndex = text.IndexOf("://", StringComparison.Ordinal); + if (endIndex == -1) + throw new UriFormatException("Text is not a URI"); + + return text.Substring(0, endIndex); + } + + private static Server ParseNetchUri(string text) + { + text = URLSafeBase64Decode(text.Substring(8)); + + var NetchLink = JsonSerializer.Deserialize(text); + + if (string.IsNullOrEmpty(NetchLink.GetProperty("Hostname").GetString())) + throw new FormatException(); + + if (!ushort.TryParse(NetchLink.GetProperty("Port").GetString(), out _)) + throw new FormatException(); + + return JsonSerializer.Deserialize(text, + new JsonSerializerOptions + { + Converters = { new ServerConverterWithTypeDiscriminator() } + })!; + } + + public static string GetNetchLink(Server s) + { + var jsonSerializerOptions = Global.NewCustomJsonSerializerOptions(); + jsonSerializerOptions.WriteIndented = false; + return "Netch://" + URLSafeBase64Encode(JsonSerializer.Serialize(s, jsonSerializerOptions)); + } + + #region Utils + + /// + /// URL 传输安全的 Base64 解码 + /// + /// 需要解码的字符串 + /// 解码后的字符串 + public static string URLSafeBase64Decode(string text) + { + return Encoding.UTF8.GetString( + Convert.FromBase64String(text.Replace("-", "+").Replace("_", "/").PadRight(text.Length + (4 - text.Length % 4) % 4, '='))); + } + + /// + /// URL 传输安全的 Base64 加密 + /// + /// 需要加密的字符串 + /// 加密后的字符串 + public static string URLSafeBase64Encode(string text) + { + return Convert.ToBase64String(Encoding.UTF8.GetBytes(text)).Replace("+", "-").Replace("/", "_").Replace("=", ""); + } + + private static string RemoveEmoji(string text) + { + byte[] emojiBytes = { 240, 159 }; + var remark = Encoding.UTF8.GetBytes(text); + var startIndex = 0; + while (remark.Length > startIndex + 1 && remark[startIndex] == emojiBytes[0] && remark[startIndex + 1] == emojiBytes[1]) + startIndex += 4; + + return Encoding.UTF8.GetString(remark.Skip(startIndex).ToArray()).Trim(); + } + + public static string UnBase64String(string value) + { + if (string.IsNullOrEmpty(value)) + return ""; + + var bytes = Convert.FromBase64String(value); + return Encoding.UTF8.GetString(bytes); + } + + public static string ToBase64String(string value) + { + if (string.IsNullOrEmpty(value)) + return ""; + + var bytes = Encoding.UTF8.GetBytes(value); + return Convert.ToBase64String(bytes); + } + + public static Dictionary ParseParam(string paramStr) + { + var paramsDict = new Dictionary(); + var obfsParams = paramStr.Split('&'); + foreach (var p in obfsParams) + if (p.IndexOf('=') > 0) + { + var index = p.IndexOf('='); + var key = p.Substring(0, index); + var val = p.Substring(index + 1); + paramsDict[key] = val; + } + + return paramsDict; + } + + public static IEnumerable GetLines(this string str, bool removeEmptyLines = true) + { + using var sr = new StringReader(str); + string? line; + while ((line = sr.ReadLine()) != null) + { + if (removeEmptyLines && string.IsNullOrWhiteSpace(line)) + continue; + + yield return line; + } + } + + #endregion } \ No newline at end of file diff --git a/Netch/Utils/Socks5ServerTestUtils.cs b/Netch/Utils/Socks5ServerTestUtils.cs index 134acc3d..1492a326 100644 --- a/Netch/Utils/Socks5ServerTestUtils.cs +++ b/Netch/Utils/Socks5ServerTestUtils.cs @@ -1,8 +1,5 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using System.Net; -using System.Threading; -using System.Threading.Tasks; using Netch.Models; using Netch.Servers; using Socks5.Models; @@ -11,100 +8,99 @@ using STUN.Enums; using STUN.Proxy; using STUN.StunResult; -namespace Netch.Utils +namespace Netch.Utils; + +public static class Socks5ServerTestUtils { - public static class Socks5ServerTestUtils + public static async Task DiscoveryNatTypeAsync(Socks5Server socks5, CancellationToken ctx = default) { - public static async Task DiscoveryNatTypeAsync(Socks5Server socks5, CancellationToken ctx = default) + var stunServer = Global.Settings.STUN_Server; + var port = (ushort)Global.Settings.STUN_Server_Port; + var local = new IPEndPoint(IPAddress.Any, 0); + + var socks5Option = new Socks5CreateOption { - var stunServer = Global.Settings.STUN_Server; - var port = (ushort)Global.Settings.STUN_Server_Port; - var local = new IPEndPoint(IPAddress.Any, 0); - - var socks5Option = new Socks5CreateOption + Address = await DnsUtils.LookupAsync(socks5.Hostname), + Port = socks5.Port, + UsernamePassword = new UsernamePassword { - Address = await DnsUtils.LookupAsync(socks5.Hostname), - Port = socks5.Port, - UsernamePassword = new UsernamePassword - { - UserName = socks5.Username, - Password = socks5.Password - } - }; - - var ip = await DnsUtils.LookupAsync(stunServer); - if (ip == null) - { - return new NatTypeTestResult { Result = "Wrong STUN Server!" }; + UserName = socks5.Username, + Password = socks5.Password } + }; - using IUdpProxy proxy = ProxyFactory.CreateProxy(ProxyType.Socks5, new IPEndPoint(IPAddress.Loopback, 0), socks5Option); - using var client = new StunClient5389UDP(new IPEndPoint(ip, port), local, proxy); - - await client.ConnectProxyAsync(ctx); - try - { - await client.QueryAsync(ctx); - } - finally - { - await client.CloseProxyAsync(ctx); - } - - var res = client.State; - var result = GetSimpleResult(res); - - return new NatTypeTestResult - { - Result = result, - LocalEnd = res.LocalEndPoint?.ToString(), - PublicEnd = res.PublicEndPoint?.ToString() - }; + var ip = await DnsUtils.LookupAsync(stunServer); + if (ip == null) + { + return new NatTypeTestResult { Result = "Wrong STUN Server!" }; } - private static string GetSimpleResult(StunResult5389 res) + using IUdpProxy proxy = ProxyFactory.CreateProxy(ProxyType.Socks5, new IPEndPoint(IPAddress.Loopback, 0), socks5Option); + using var client = new StunClient5389UDP(new IPEndPoint(ip, port), local, proxy); + + await client.ConnectProxyAsync(ctx); + try { - switch (res.BindingTestResult, res.MappingBehavior, res.FilteringBehavior) - { - case (BindingTestResult.Fail, _, _): - return "NoUDP"; - case (not BindingTestResult.Success, _, _): - return res.BindingTestResult.ToString(); - case (_, MappingBehavior.Direct or MappingBehavior.EndpointIndependent, FilteringBehavior.EndpointIndependent): - return "1"; - case (_, MappingBehavior.Direct or MappingBehavior.EndpointIndependent, _): - return "2"; - case (_, MappingBehavior.AddressDependent or MappingBehavior.AddressAndPortDependent, _): - return "3"; - case (_, MappingBehavior.Fail, _): - return MappingBehavior.Fail.ToString(); - default: - return res.FilteringBehavior.ToString(); - } + await client.QueryAsync(ctx); + } + finally + { + await client.CloseProxyAsync(ctx); } - public static async Task HttpConnectAsync(Socks5Server socks5, CancellationToken ctx) + var res = client.State; + var result = GetSimpleResult(res); + + return new NatTypeTestResult { - var socks5Option = new Socks5CreateOption - { - Address = await DnsUtils.LookupAsync(socks5.Hostname), - Port = socks5.Port, - UsernamePassword = new UsernamePassword - { - UserName = socks5.Username, - Password = socks5.Password - } - }; + Result = result, + LocalEnd = res.LocalEndPoint?.ToString(), + PublicEnd = res.PublicEndPoint?.ToString() + }; + } - var stopwatch = Stopwatch.StartNew(); - - var result = await Socks5.Utils.Socks5TestUtils.Socks5ConnectAsync(socks5Option, token: ctx); - - stopwatch.Stop(); - if (result) - return Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); - - return null; + private static string GetSimpleResult(StunResult5389 res) + { + switch (res.BindingTestResult, res.MappingBehavior, res.FilteringBehavior) + { + case (BindingTestResult.Fail, _, _): + return "NoUDP"; + case (not BindingTestResult.Success, _, _): + return res.BindingTestResult.ToString(); + case (_, MappingBehavior.Direct or MappingBehavior.EndpointIndependent, FilteringBehavior.EndpointIndependent): + return "1"; + case (_, MappingBehavior.Direct or MappingBehavior.EndpointIndependent, _): + return "2"; + case (_, MappingBehavior.AddressDependent or MappingBehavior.AddressAndPortDependent, _): + return "3"; + case (_, MappingBehavior.Fail, _): + return MappingBehavior.Fail.ToString(); + default: + return res.FilteringBehavior.ToString(); } } + + public static async Task HttpConnectAsync(Socks5Server socks5, CancellationToken ctx) + { + var socks5Option = new Socks5CreateOption + { + Address = await DnsUtils.LookupAsync(socks5.Hostname), + Port = socks5.Port, + UsernamePassword = new UsernamePassword + { + UserName = socks5.Username, + Password = socks5.Password + } + }; + + var stopwatch = Stopwatch.StartNew(); + + var result = await Socks5.Utils.Socks5TestUtils.Socks5ConnectAsync(socks5Option, token: ctx); + + stopwatch.Stop(); + if (result) + return Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); + + return null; + } } \ No newline at end of file diff --git a/Netch/Utils/StringExtension.cs b/Netch/Utils/StringExtension.cs index 0ed59a2c..8ee4211d 100644 --- a/Netch/Utils/StringExtension.cs +++ b/Netch/Utils/StringExtension.cs @@ -1,86 +1,81 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; +using System.Text; -namespace Netch.Utils +namespace Netch.Utils; + +public static class StringExtension { - public static class StringExtension + public static bool IsNullOrEmpty(this string? value) { - public static bool IsNullOrEmpty(this string? value) + return string.IsNullOrEmpty(value); + } + + public static bool IsNullOrWhiteSpace(this string? value) + { + return string.IsNullOrWhiteSpace(value); + } + + public static bool BeginWithAny(this string s, IEnumerable chars) + { + if (s.IsNullOrEmpty()) + return false; + + return chars.Contains(s[0]); + } + + public static bool IsWhiteSpace(this string value) + { + return value.All(char.IsWhiteSpace); + } + + public static IEnumerable NonWhiteSpaceLines(this TextReader reader) + { + string? line; + while ((line = reader.ReadLine()) != null) { - return string.IsNullOrEmpty(value); - } + if (line.IsWhiteSpace()) + continue; - public static bool IsNullOrWhiteSpace(this string? value) - { - return string.IsNullOrWhiteSpace(value); - } - - public static bool BeginWithAny(this string s, IEnumerable chars) - { - if (s.IsNullOrEmpty()) - return false; - - return chars.Contains(s[0]); - } - - public static bool IsWhiteSpace(this string value) - { - return value.All(char.IsWhiteSpace); - } - - public static IEnumerable NonWhiteSpaceLines(this TextReader reader) - { - string? line; - while ((line = reader.ReadLine()) != null) - { - if (line.IsWhiteSpace()) - continue; - - yield return line; - } - } - - public static string ToRegexString(this string value) - { - var sb = new StringBuilder(); - foreach (var t in value) - { - var escapeCharacters = new[] { '\\', '*', '+', '?', '|', '{', '}', '[', ']', '(', ')', '^', '$', '.' }; - if (escapeCharacters.Any(s => s == t)) - sb.Append('\\'); - - sb.Append(t); - } - - return sb.ToString(); - } - - public static string[] SplitRemoveEmptyEntriesAndTrimEntries(this string value, params char[] separator) - { - return value.Split(separator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - } - - public static string[] SplitTrimEntries(this string value, params char[] separator) - { - return value.Split(separator, StringSplitOptions.TrimEntries); - } - - public static string[] SplitRemoveEmptyEntries(this string value, params char[] separator) - { - return value.Split(separator, StringSplitOptions.RemoveEmptyEntries); - } - - public static string? ValueOrDefault(this string? value, string? defaultValue = default) - { - return string.IsNullOrWhiteSpace(value) ? defaultValue : value; - } - - public static string[]? SplitOrDefault(this string? value) - { - return !string.IsNullOrWhiteSpace(value) ? value.Split(',') : default; + yield return line; } } + + public static string ToRegexString(this string value) + { + var sb = new StringBuilder(); + foreach (var t in value) + { + var escapeCharacters = new[] { '\\', '*', '+', '?', '|', '{', '}', '[', ']', '(', ')', '^', '$', '.' }; + if (escapeCharacters.Any(s => s == t)) + sb.Append('\\'); + + sb.Append(t); + } + + return sb.ToString(); + } + + public static string[] SplitRemoveEmptyEntriesAndTrimEntries(this string value, params char[] separator) + { + return value.Split(separator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + } + + public static string[] SplitTrimEntries(this string value, params char[] separator) + { + return value.Split(separator, StringSplitOptions.TrimEntries); + } + + public static string[] SplitRemoveEmptyEntries(this string value, params char[] separator) + { + return value.Split(separator, StringSplitOptions.RemoveEmptyEntries); + } + + public static string? ValueOrDefault(this string? value, string? defaultValue = default) + { + return string.IsNullOrWhiteSpace(value) ? defaultValue : value; + } + + public static string[]? SplitOrDefault(this string? value) + { + return !string.IsNullOrWhiteSpace(value) ? value.Split(',') : default; + } } \ No newline at end of file diff --git a/Netch/Utils/SubscriptionUtil.cs b/Netch/Utils/SubscriptionUtil.cs index 44d6deb3..0aa2fde8 100644 --- a/Netch/Utils/SubscriptionUtil.cs +++ b/Netch/Utils/SubscriptionUtil.cs @@ -1,61 +1,55 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Net; -using System.Threading.Tasks; using Netch.Models; -using Serilog; -namespace Netch.Utils +namespace Netch.Utils; + +public static class SubscriptionUtil { - public static class SubscriptionUtil + private static readonly object ServerLock = new(); + + public static async Task UpdateServersAsync(string? proxyServer = default) { - private static readonly object ServerLock = new(); + await Task.WhenAll(Global.Settings.Subscription.Select(item => UpdateServerCoreAsync(item, proxyServer))); + } - public static async Task UpdateServersAsync(string? proxyServer = default) + private static async Task UpdateServerCoreAsync(Subscription item, string? proxyServer) + { + try { - await Task.WhenAll(Global.Settings.Subscription.Select(item => UpdateServerCoreAsync(item, proxyServer))); + if (!item.Enable) + return; + + var request = WebUtil.CreateRequest(item.Link); + + if (!string.IsNullOrEmpty(item.UserAgent)) + request.UserAgent = item.UserAgent; + + if (!string.IsNullOrEmpty(proxyServer)) + request.Proxy = new WebProxy(proxyServer); + + List servers; + + var (code, result) = await WebUtil.DownloadStringAsync(request); + if (code == HttpStatusCode.OK) + servers = ShareLink.ParseText(result); + else + throw new Exception($"{item.Remark} Response Status Code: {code}"); + + foreach (var server in servers) + server.Group = item.Remark; + + lock (ServerLock) + { + Global.Settings.Server.RemoveAll(server => server.Group.Equals(item.Remark)); + Global.Settings.Server.AddRange(servers); + } + + Global.MainForm.NotifyTip(i18N.TranslateFormat("Update {1} server(s) from {0}", item.Remark, servers.Count)); } - - private static async Task UpdateServerCoreAsync(Subscription item, string? proxyServer) + catch (Exception e) { - try - { - if (!item.Enable) - return; - - var request = WebUtil.CreateRequest(item.Link); - - if (!string.IsNullOrEmpty(item.UserAgent)) - request.UserAgent = item.UserAgent; - - if (!string.IsNullOrEmpty(proxyServer)) - request.Proxy = new WebProxy(proxyServer); - - List servers; - - var (code, result) = await WebUtil.DownloadStringAsync(request); - if (code == HttpStatusCode.OK) - servers = ShareLink.ParseText(result); - else - throw new Exception($"{item.Remark} Response Status Code: {code}"); - - foreach (var server in servers) - server.Group = item.Remark; - - lock (ServerLock) - { - Global.Settings.Server.RemoveAll(server => server.Group.Equals(item.Remark)); - Global.Settings.Server.AddRange(servers); - } - - Global.MainForm.NotifyTip(i18N.TranslateFormat("Update {1} server(s) from {0}", item.Remark, servers.Count)); - } - catch (Exception e) - { - Global.MainForm.NotifyTip($"{i18N.TranslateFormat("Update servers failed from {0}", item.Remark)}\n{e.Message}", info: false); - Log.Warning(e, "Update servers failed"); - } + Global.MainForm.NotifyTip($"{i18N.TranslateFormat("Update servers failed from {0}", item.Remark)}\n{e.Message}", info: false); + Log.Warning(e, "Update servers failed"); } } } \ No newline at end of file diff --git a/Netch/Utils/SystemInfo.cs b/Netch/Utils/SystemInfo.cs index 7fc8b899..c7ede64f 100644 --- a/Netch/Utils/SystemInfo.cs +++ b/Netch/Utils/SystemInfo.cs @@ -1,67 +1,62 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; +using System.Diagnostics; using System.Management; -namespace Netch.Utils -{ - public static class SystemInfo - { - public static IEnumerable SystemDrivers(bool allDriver) - { - var mc = new ManagementClass("Win32_SystemDriver"); - foreach (var obj in mc.GetInstances().Cast()) - { - if (!(bool)obj["Started"]) - continue; +namespace Netch.Utils; - var path = obj["PathName"].ToString(); +public static class SystemInfo +{ + public static IEnumerable SystemDrivers(bool allDriver) + { + var mc = new ManagementClass("Win32_SystemDriver"); + foreach (var obj in mc.GetInstances().Cast()) + { + if (!(bool)obj["Started"]) + continue; + + var path = obj["PathName"].ToString(); + if (path == null) + continue; + + var vendorExclude = new[] { "microsoft", "intel", "amd", "nvidia", "realtek" }; + var vendorName = FileVersionInfo.GetVersionInfo(path).LegalCopyright ?? string.Empty; + if (!allDriver && vendorExclude.Any(s => vendorName.Contains(s, StringComparison.OrdinalIgnoreCase))) + continue; + + yield return $"{obj["Caption"]} [{obj["Description"]}]({vendorName})\n\t{obj["PathName"]}"; + } + } + + public static IEnumerable Processes(bool mask) + { + var sortedSet = new SortedSet(); + var windowsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Windows); + var windowsAppsFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsApps"); + var userProfileFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + foreach (var process in Process.GetProcesses()) + { + try + { + var path = process.MainModule?.FileName; if (path == null) continue; - var vendorExclude = new[] { "microsoft", "intel", "amd", "nvidia", "realtek" }; - var vendorName = FileVersionInfo.GetVersionInfo(path).LegalCopyright ?? string.Empty; - if (!allDriver && vendorExclude.Any(s => vendorName.Contains(s, StringComparison.OrdinalIgnoreCase))) + if (path.StartsWith(windowsFolder, StringComparison.OrdinalIgnoreCase)) continue; - yield return $"{obj["Caption"]} [{obj["Description"]}]({vendorName})\n\t{obj["PathName"]}"; - } - } + if (path.StartsWith(windowsAppsFolder, StringComparison.OrdinalIgnoreCase)) + continue; - public static IEnumerable Processes(bool mask) - { - var sortedSet = new SortedSet(); - var windowsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Windows); - var windowsAppsFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsApps"); - var userProfileFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - foreach (var process in Process.GetProcesses()) + if (mask) + sortedSet.Add(path.Replace(userProfileFolder, "%USERPROFILE%")); + else + sortedSet.Add(path); + } + catch (Exception) { - try - { - var path = process.MainModule?.FileName; - if (path == null) - continue; - - if (path.StartsWith(windowsFolder, StringComparison.OrdinalIgnoreCase)) - continue; - - if (path.StartsWith(windowsAppsFolder, StringComparison.OrdinalIgnoreCase)) - continue; - - if (mask) - sortedSet.Add(path.Replace(userProfileFolder, "%USERPROFILE%")); - else - sortedSet.Add(path); - } - catch (Exception) - { - // ignored - } + // ignored } - - return sortedSet; } + + return sortedSet; } } \ No newline at end of file diff --git a/Netch/Utils/Utils.cs b/Netch/Utils/Utils.cs index ddbf3cf3..144bea81 100644 --- a/Netch/Utils/Utils.cs +++ b/Netch/Utils/Utils.cs @@ -1,277 +1,268 @@ -using System; using System.ComponentModel; using System.Diagnostics; -using System.Drawing; -using System.IO; -using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; using MaxMind.GeoIP2; using Microsoft.Win32.TaskScheduler; -using Serilog; using Task = System.Threading.Tasks.Task; -namespace Netch.Utils +namespace Netch.Utils; + +public static class Utils { - public static class Utils + public static void Open(string path) { - public static void Open(string path) + try { - try + Process.Start(new ProcessStartInfo { - Process.Start(new ProcessStartInfo - { - FileName = "explorer.exe", - Arguments = path, - UseShellExecute = true - }); - } - catch (Exception e) - { - Log.Warning(e, "Open \"{Uri}\" failed", path); - } + FileName = "explorer.exe", + Arguments = path, + UseShellExecute = true + }); + } + catch (Exception e) + { + Log.Warning(e, "Open \"{Uri}\" failed", path); + } + } + + public static async Task TCPingAsync(IPAddress ip, int port, int timeout = 1000, CancellationToken ct = default) + { + using var client = new TcpClient(ip.AddressFamily); + + var stopwatch = Stopwatch.StartNew(); + + var task = client.ConnectAsync(ip, port); + + var resTask = await Task.WhenAny(task, Task.Delay(timeout, ct)); + + stopwatch.Stop(); + if (resTask == task && client.Connected) + { + var t = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); + return t; } - public static async Task TCPingAsync(IPAddress ip, int port, int timeout = 1000, CancellationToken ct = default) + return timeout; + } + + public static async Task ICMPingAsync(IPAddress ip, int timeout = 1000) + { + var reply = await new Ping().SendPingAsync(ip, timeout); + + if (reply.Status == IPStatus.Success) + return Convert.ToInt32(reply.RoundtripTime); + + return timeout; + } + + public static async Task GetCityCodeAsync(string address) + { + var i = address.IndexOf(':'); + if (i != -1) + address = address[..i]; + + string? country = null; + try { - using var client = new TcpClient(ip.AddressFamily); + var databaseReader = new DatabaseReader("bin\\GeoLite2-Country.mmdb"); - var stopwatch = Stopwatch.StartNew(); - - var task = client.ConnectAsync(ip, port); - - var resTask = await Task.WhenAny(task, Task.Delay(timeout, ct)); - - stopwatch.Stop(); - if (resTask == task && client.Connected) + if (IPAddress.TryParse(address, out _)) { - var t = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); - return t; + country = databaseReader.Country(address).Country.IsoCode; } - - return timeout; - } - - public static async Task ICMPingAsync(IPAddress ip, int timeout = 1000) - { - var reply = await new Ping().SendPingAsync(ip, timeout); - - if (reply.Status == IPStatus.Success) - return Convert.ToInt32(reply.RoundtripTime); - - return timeout; - } - - public static async Task GetCityCodeAsync(string address) - { - var i = address.IndexOf(':'); - if (i != -1) - address = address[..i]; - - string? country = null; - try + else { - var databaseReader = new DatabaseReader("bin\\GeoLite2-Country.mmdb"); + var dnsResult = await DnsUtils.LookupAsync(address); - if (IPAddress.TryParse(address, out _)) - { - country = databaseReader.Country(address).Country.IsoCode; - } - else - { - var dnsResult = await DnsUtils.LookupAsync(address); - - if (dnsResult != null) - country = databaseReader.Country(dnsResult).Country.IsoCode; - } - } - catch - { - // ignored - } - - country ??= "Unknown"; - - return country; - } - - public static string SHA256CheckSum(string filePath) - { - try - { - using var fileStream = File.OpenRead(filePath); - return SHA256ComputeCore(fileStream); - } - catch (Exception e) - { - Log.Warning(e, $"Compute file \"{filePath}\" sha256 failed"); - return ""; + if (dnsResult != null) + country = databaseReader.Country(dnsResult).Country.IsoCode; } } - - private static string SHA256ComputeCore(Stream stream) + catch { - using var sha256 = SHA256.Create(); - return string.Concat(sha256.ComputeHash(stream).Select(b => b.ToString("x2"))); + // ignored } - public static string GetFileVersion(string file) - { - if (File.Exists(file)) - return FileVersionInfo.GetVersionInfo(file).FileVersion ?? ""; + country ??= "Unknown"; + return country; + } + + public static string SHA256CheckSum(string filePath) + { + try + { + using var fileStream = File.OpenRead(filePath); + return SHA256ComputeCore(fileStream); + } + catch (Exception e) + { + Log.Warning(e, $"Compute file \"{filePath}\" sha256 failed"); return ""; } + } - public static void DrawCenterComboBox(object sender, DrawItemEventArgs e) + private static string SHA256ComputeCore(Stream stream) + { + using var sha256 = SHA256.Create(); + return string.Concat(sha256.ComputeHash(stream).Select(b => b.ToString("x2"))); + } + + public static string GetFileVersion(string file) + { + if (File.Exists(file)) + return FileVersionInfo.GetVersionInfo(file).FileVersion ?? ""; + + return ""; + } + + public static void DrawCenterComboBox(object sender, DrawItemEventArgs e) + { + if (sender is ComboBox cbx) { - if (sender is ComboBox cbx) - { - e.DrawBackground(); + e.DrawBackground(); - if (e.Index < 0) - return; + if (e.Index < 0) + return; - TextRenderer.DrawText(e.Graphics, - cbx.Items[e.Index].ToString(), - cbx.Font, - e.Bounds, - (e.State & DrawItemState.Selected) == DrawItemState.Selected ? SystemColors.HighlightText : cbx.ForeColor, - TextFormatFlags.HorizontalCenter); - } + TextRenderer.DrawText(e.Graphics, + cbx.Items[e.Index].ToString(), + cbx.Font, + e.Bounds, + (e.State & DrawItemState.Selected) == DrawItemState.Selected ? SystemColors.HighlightText : cbx.ForeColor, + TextFormatFlags.HorizontalCenter); } + } - public static void ComponentIterator(in Component component, in Action func) + public static void ComponentIterator(in Component component, in Action func) + { + func.Invoke(component); + switch (component) { - func.Invoke(component); - switch (component) - { - case ListView listView: - // ListView sub item - foreach (var item in listView.Columns.Cast()) - ComponentIterator(item, func); + case ListView listView: + // ListView sub item + foreach (var item in listView.Columns.Cast()) + ComponentIterator(item, func); - break; - case ToolStripMenuItem toolStripMenuItem: - // Iterator Menu strip sub item - foreach (var item in toolStripMenuItem.DropDownItems.Cast()) - ComponentIterator(item, func); + break; + case ToolStripMenuItem toolStripMenuItem: + // Iterator Menu strip sub item + foreach (var item in toolStripMenuItem.DropDownItems.Cast()) + ComponentIterator(item, func); - break; - case MenuStrip menuStrip: - // Menu Strip - foreach (var item in menuStrip.Items.Cast()) - ComponentIterator(item, func); + break; + case MenuStrip menuStrip: + // Menu Strip + foreach (var item in menuStrip.Items.Cast()) + ComponentIterator(item, func); - break; - case ContextMenuStrip contextMenuStrip: - foreach (var item in contextMenuStrip.Items.Cast()) - ComponentIterator(item, func); + break; + case ContextMenuStrip contextMenuStrip: + foreach (var item in contextMenuStrip.Items.Cast()) + ComponentIterator(item, func); - break; - case Control control: - foreach (var c in control.Controls.Cast()) - ComponentIterator(c, func); + break; + case Control control: + foreach (var c in control.Controls.Cast()) + ComponentIterator(c, func); - if (control.ContextMenuStrip != null) - ComponentIterator(control.ContextMenuStrip, func); + if (control.ContextMenuStrip != null) + ComponentIterator(control.ContextMenuStrip, func); - break; - } + break; } + } - public static void RegisterNetchStartupItem() + public static void RegisterNetchStartupItem() + { + const string TaskName = "Netch Startup"; + var folder = TaskService.Instance.GetFolder("\\"); + var taskIsExists = folder.Tasks.Any(task => task.Name == TaskName); + + if (Global.Settings.RunAtStartup) { - const string TaskName = "Netch Startup"; - var folder = TaskService.Instance.GetFolder("\\"); - var taskIsExists = folder.Tasks.Any(task => task.Name == TaskName); + if (taskIsExists) + folder.DeleteTask(TaskName, false); - if (Global.Settings.RunAtStartup) - { - if (taskIsExists) - folder.DeleteTask(TaskName, false); + var td = TaskService.Instance.NewTask(); - var td = TaskService.Instance.NewTask(); + td.RegistrationInfo.Author = "Netch"; + td.RegistrationInfo.Description = "Netch run at startup."; + td.Principal.RunLevel = TaskRunLevel.Highest; - td.RegistrationInfo.Author = "Netch"; - td.RegistrationInfo.Description = "Netch run at startup."; - td.Principal.RunLevel = TaskRunLevel.Highest; + td.Triggers.Add(new LogonTrigger()); + td.Actions.Add(new ExecAction(Global.NetchExecutable)); - td.Triggers.Add(new LogonTrigger()); - td.Actions.Add(new ExecAction(Global.NetchExecutable)); + td.Settings.ExecutionTimeLimit = TimeSpan.Zero; + td.Settings.DisallowStartIfOnBatteries = false; + td.Settings.StopIfGoingOnBatteries = false; + td.Settings.IdleSettings.StopOnIdleEnd = false; + td.Settings.IdleSettings.RestartOnIdle = false; + td.Settings.RunOnlyIfIdle = false; + td.Settings.Compatibility = TaskCompatibility.V2_1; - td.Settings.ExecutionTimeLimit = TimeSpan.Zero; - td.Settings.DisallowStartIfOnBatteries = false; - td.Settings.StopIfGoingOnBatteries = false; - td.Settings.IdleSettings.StopOnIdleEnd = false; - td.Settings.IdleSettings.RestartOnIdle = false; - td.Settings.RunOnlyIfIdle = false; - td.Settings.Compatibility = TaskCompatibility.V2_1; - - TaskService.Instance.RootFolder.RegisterTaskDefinition("Netch Startup", td); - } - else - { - if (taskIsExists) - folder.DeleteTask(TaskName, false); - } + TaskService.Instance.RootFolder.RegisterTaskDefinition("Netch Startup", td); } - - public static void ChangeControlForeColor(Component component, Color color) + else { - switch (component) + if (taskIsExists) + folder.DeleteTask(TaskName, false); + } + } + + public static void ChangeControlForeColor(Component component, Color color) + { + switch (component) + { + case TextBox _: + case ComboBox _: + if (((Control)component).ForeColor != color) + ((Control)component).ForeColor = color; + + break; + } + } + + public static int SubnetToCidr(string value) + { + var subnet = IPAddress.Parse(value); + return SubnetToCidr(subnet); + } + + public static int SubnetToCidr(IPAddress subnet) + { + return subnet.GetAddressBytes().Sum(b => Convert.ToString(b, 2).Count(c => c == '1')); + } + + public static string GetHostFromUri(string str) + { + var startIndex = str.LastIndexOf('/'); + if (startIndex != -1) + str = str[(startIndex + 1)..]; + + var endIndex = str.IndexOf(':'); + return endIndex == -1 ? str : str[..endIndex]; + } + + public static void ActivateVisibleWindows() + { + var forms = Application.OpenForms.Cast
().Where(f => f.Visible).ToList(); + if (!forms.Any()) + { + Global.MainForm.Show(); + Global.MainForm.WindowState = FormWindowState.Normal; + Global.MainForm.Activate(); + } + else + { + foreach (var f in forms) { - case TextBox _: - case ComboBox _: - if (((Control)component).ForeColor != color) - ((Control)component).ForeColor = color; - - break; - } - } - - public static int SubnetToCidr(string value) - { - var subnet = IPAddress.Parse(value); - return SubnetToCidr(subnet); - } - - public static int SubnetToCidr(IPAddress subnet) - { - return subnet.GetAddressBytes().Sum(b => Convert.ToString(b, 2).Count(c => c == '1')); - } - - public static string GetHostFromUri(string str) - { - var startIndex = str.LastIndexOf('/'); - if (startIndex != -1) - str = str[(startIndex + 1)..]; - - var endIndex = str.IndexOf(':'); - return endIndex == -1 ? str : str[..endIndex]; - } - - public static void ActivateVisibleWindows() - { - var forms = Application.OpenForms.Cast().Where(f => f.Visible).ToList(); - if (!forms.Any()) - { - Global.MainForm.Show(); - Global.MainForm.WindowState = FormWindowState.Normal; - Global.MainForm.Activate(); - } - else - { - foreach (var f in forms) - { - f.WindowState = FormWindowState.Normal; - f.Activate(); - } + f.WindowState = FormWindowState.Normal; + f.Activate(); } } } diff --git a/Netch/Utils/WebUtil.cs b/Netch/Utils/WebUtil.cs index f22fe740..84c9cde6 100644 --- a/Netch/Utils/WebUtil.cs +++ b/Netch/Utils/WebUtil.cs @@ -1,118 +1,114 @@ -using System; -using System.IO; using System.Net; using System.Text; -using System.Threading.Tasks; using Microsoft.VisualStudio.Threading; -namespace Netch.Utils +namespace Netch.Utils; + +public static class WebUtil { - public static class WebUtil + public const string DefaultUserAgent = + @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36 Edg/94.0.992.31"; + + static WebUtil() { - public const string DefaultUserAgent = - @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36 Edg/94.0.992.31"; + ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; + } - static WebUtil() + private static int DefaultGetTimeout => Global.Settings.RequestTimeout; + + public static HttpWebRequest CreateRequest(string url, int? timeout = null, string? userAgent = null) + { + var req = (HttpWebRequest)WebRequest.Create(url); + req.UserAgent = string.IsNullOrWhiteSpace(userAgent) ? DefaultUserAgent : userAgent; + req.Accept = "*/*"; + req.KeepAlive = true; + req.Timeout = timeout ?? DefaultGetTimeout; + req.ReadWriteTimeout = timeout ?? DefaultGetTimeout; + req.Headers.Add("Accept-Charset", "utf-8"); + return req; + } + + /// + /// 异步下载 + /// + /// + /// + public static async Task DownloadBytesAsync(HttpWebRequest req) + { + using var webResponse = await req.GetResponseAsync(); + await using var memoryStream = new MemoryStream(); + await using var input = webResponse.GetResponseStream(); + + await input.CopyToAsync(memoryStream); + return memoryStream.ToArray(); + } + + /// + /// 异步下载并编码为字符串 + /// + /// + /// 编码,默认UTF-8 + /// + public static (HttpStatusCode, string) DownloadString(HttpWebRequest req, Encoding? encoding = null) + { + encoding ??= Encoding.UTF8; + using var rep = (HttpWebResponse)req.GetResponse(); + using var responseStream = rep.GetResponseStream(); + using var streamReader = new StreamReader(responseStream, encoding); + + return (rep.StatusCode, streamReader.ReadToEnd()); + } + + /// + /// 异步下载并编码为字符串 + /// + /// + /// 编码,默认UTF-8 + /// + public static async Task<(HttpStatusCode, string)> DownloadStringAsync(HttpWebRequest req, Encoding? encoding = null) + { + encoding ??= Encoding.UTF8; + using var webResponse = (HttpWebResponse)await req.GetResponseAsync(); + await using var responseStream = webResponse.GetResponseStream(); + using var streamReader = new StreamReader(responseStream, encoding); + + return (webResponse.StatusCode, await streamReader.ReadToEndAsync()); + } + + public static async Task DownloadFileAsync(string address, string fileFullPath, IProgress? progress = null) + { + await DownloadFileAsync(CreateRequest(address), fileFullPath, progress); + } + + public static async Task DownloadFileAsync(HttpWebRequest req, string fileFullPath, IProgress? progress) + { + await using (var fileStream = File.Open(fileFullPath, FileMode.Create, FileAccess.Write)) + using (var webResponse = (HttpWebResponse)await req.GetResponseAsync()) + await using (var input = webResponse.GetResponseStream()) + using (var downloadTask = input.CopyToAsync(fileStream)) { - ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; + if (progress != null) + ReportProgressAsync(webResponse.ContentLength, downloadTask, fileStream, progress, 200).Forget(); + + await downloadTask; } - private static int DefaultGetTimeout => Global.Settings.RequestTimeout; + progress?.Report(100); + } - public static HttpWebRequest CreateRequest(string url, int? timeout = null, string? userAgent = null) + private static async Task ReportProgressAsync(long total, IAsyncResult downloadTask, Stream stream, IProgress progress, int interval) + { + var n = 0; + while (!downloadTask.IsCompleted) { - var req = (HttpWebRequest)WebRequest.Create(url); - req.UserAgent = string.IsNullOrWhiteSpace(userAgent) ? DefaultUserAgent : userAgent; - req.Accept = "*/*"; - req.KeepAlive = true; - req.Timeout = timeout ?? DefaultGetTimeout; - req.ReadWriteTimeout = timeout ?? DefaultGetTimeout; - req.Headers.Add("Accept-Charset", "utf-8"); - return req; - } - - /// - /// 异步下载 - /// - /// - /// - public static async Task DownloadBytesAsync(HttpWebRequest req) - { - using var webResponse = await req.GetResponseAsync(); - await using var memoryStream = new MemoryStream(); - await using var input = webResponse.GetResponseStream(); - - await input.CopyToAsync(memoryStream); - return memoryStream.ToArray(); - } - - /// - /// 异步下载并编码为字符串 - /// - /// - /// 编码,默认UTF-8 - /// - public static (HttpStatusCode, string) DownloadString(HttpWebRequest req, Encoding? encoding = null) - { - encoding ??= Encoding.UTF8; - using var rep = (HttpWebResponse)req.GetResponse(); - using var responseStream = rep.GetResponseStream(); - using var streamReader = new StreamReader(responseStream, encoding); - - return (rep.StatusCode, streamReader.ReadToEnd()); - } - - /// - /// 异步下载并编码为字符串 - /// - /// - /// 编码,默认UTF-8 - /// - public static async Task<(HttpStatusCode, string)> DownloadStringAsync(HttpWebRequest req, Encoding? encoding = null) - { - encoding ??= Encoding.UTF8; - using var webResponse = (HttpWebResponse)await req.GetResponseAsync(); - await using var responseStream = webResponse.GetResponseStream(); - using var streamReader = new StreamReader(responseStream, encoding); - - return (webResponse.StatusCode, await streamReader.ReadToEndAsync()); - } - - public static async Task DownloadFileAsync(string address, string fileFullPath, IProgress? progress = null) - { - await DownloadFileAsync(CreateRequest(address), fileFullPath, progress); - } - - public static async Task DownloadFileAsync(HttpWebRequest req, string fileFullPath, IProgress? progress) - { - await using (var fileStream = File.Open(fileFullPath, FileMode.Create, FileAccess.Write)) - using (var webResponse = (HttpWebResponse)await req.GetResponseAsync()) - await using (var input = webResponse.GetResponseStream()) - using (var downloadTask = input.CopyToAsync(fileStream)) + var n1 = (int)((double)stream.Length / total * 100); + if (n != n1) { - if (progress != null) - ReportProgressAsync(webResponse.ContentLength, downloadTask, fileStream, progress, 200).Forget(); - - await downloadTask; + n = n1; + progress.Report(n); } - progress?.Report(100); - } - - private static async Task ReportProgressAsync(long total, IAsyncResult downloadTask, Stream stream, IProgress progress, int interval) - { - var n = 0; - while (!downloadTask.IsCompleted) - { - var n1 = (int)((double)stream.Length / total * 100); - if (n != n1) - { - n = n1; - progress.Report(n); - } - - await Task.Delay(interval).ConfigureAwait(false); - } + await Task.Delay(interval).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/Netch/Utils/i18N.cs b/Netch/Utils/i18N.cs index 1d1d81cf..0946860e 100644 --- a/Netch/Utils/i18N.cs +++ b/Netch/Utils/i18N.cs @@ -1,140 +1,134 @@ using System.Collections; -using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Linq; using System.Text; using System.Text.Json; -using System.Windows.Forms; using Netch.Properties; -using Serilog; -namespace Netch.Utils +namespace Netch.Utils; + +public static class i18N { - public static class i18N + static i18N() { - static i18N() + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } + + /// + /// 数据 + /// + public static Hashtable Data = new(); + + public static string LangCode { get; private set; } = "en-US"; + + /// + /// 加载 + /// + /// 语言代码 + public static void Load(string value) + { + string text; + var languages = GetTranslateList().Skip(1).ToList(); + + LangCode = value.Equals("System") ? CultureInfo.CurrentCulture.Name : value; + + if (!languages.Contains(LangCode)) { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + var oldLangCode = LangCode; + LangCode = languages.FirstOrDefault(s => GetLanguage(s).Equals(GetLanguage(LangCode))) ?? "en-US"; + Log.Information("Not found language {OldLangCode}, use {LangCode} instead", oldLangCode, LangCode); } - /// - /// 数据 - /// - public static Hashtable Data = new(); - - public static string LangCode { get; private set; } = "en-US"; - - /// - /// 加载 - /// - /// 语言代码 - public static void Load(string value) + switch (LangCode) { - string text; - var languages = GetTranslateList().Skip(1).ToList(); - - LangCode = value.Equals("System") ? CultureInfo.CurrentCulture.Name : value; - - if (!languages.Contains(LangCode)) - { - var oldLangCode = LangCode; - LangCode = languages.FirstOrDefault(s => GetLanguage(s).Equals(GetLanguage(LangCode))) ?? "en-US"; - Log.Information("Not found language {OldLangCode}, use {LangCode} instead", oldLangCode, LangCode); - } - - switch (LangCode) - { - case "en-US": - Data.Clear(); - return; - case "zh-CN": - text = Encoding.UTF8.GetString(Resources.zh_CN); - break; - default: - text = File.ReadAllText($"i18n\\{LangCode}"); - break; - } - - var dictionary = JsonSerializer.Deserialize>(text)!; - - if (!dictionary.Any()) - { - Log.Error("Invalid language file \"{LangCode}\"", LangCode); + case "en-US": + Data.Clear(); return; - } - - Data = new Hashtable(); - foreach (var v in dictionary) - Data.Add(v.Key, v.Value); + case "zh-CN": + text = Encoding.UTF8.GetString(Resources.zh_CN); + break; + default: + text = File.ReadAllText($"i18n\\{LangCode}"); + break; } - private static string GetLanguage(string culture) - { - if (!culture.Contains('-')) - return ""; + var dictionary = JsonSerializer.Deserialize>(text)!; - return culture.Substring(0, culture.IndexOf('-')); + if (!dictionary.Any()) + { + Log.Error("Invalid language file \"{LangCode}\"", LangCode); + return; } - /// - /// 翻译 - /// - /// 需要翻译的文本 - /// 翻译完毕的文本 - public static string Translate(params object[] text) - { - var a = new StringBuilder(); - foreach (var t in text) - if (t is string) - a.Append(Data[t]?.ToString() ?? t); - else - a.Append(t); + Data = new Hashtable(); + foreach (var v in dictionary) + Data.Add(v.Key, v.Value); + } - return a.ToString(); - } + private static string GetLanguage(string culture) + { + if (!culture.Contains('-')) + return ""; - public static string TranslateFormat(string format, params object[] args) - { - for (var i = 0; i < args.Length; i++) - if (args[i] is string) - args[i] = Translate((string)args[i]); + return culture.Substring(0, culture.IndexOf('-')); + } - return string.Format(Translate(format), args); - } + /// + /// 翻译 + /// + /// 需要翻译的文本 + /// 翻译完毕的文本 + public static string Translate(params object[] text) + { + var a = new StringBuilder(); + foreach (var t in text) + if (t is string) + a.Append(Data[t]?.ToString() ?? t); + else + a.Append(t); - public static List GetTranslateList() - { - var translateFile = new List { "System", "zh-CN", "en-US" }; + return a.ToString(); + } - if (!Directory.Exists("i18n")) - return translateFile; + public static string TranslateFormat(string format, params object[] args) + { + for (var i = 0; i < args.Length; i++) + if (args[i] is string) + args[i] = Translate((string)args[i]); - translateFile.AddRange(Directory.GetFiles("i18n", "*").Select(fileName => fileName.Substring(5))); + return string.Format(Translate(format), args); + } + + public static List GetTranslateList() + { + var translateFile = new List { "System", "zh-CN", "en-US" }; + + if (!Directory.Exists("i18n")) return translateFile; - } - public static void TranslateForm(in Control c) - { - Utils.ComponentIterator(c, - component => + translateFile.AddRange(Directory.GetFiles("i18n", "*").Select(fileName => fileName.Substring(5))); + return translateFile; + } + + public static void TranslateForm(in Control c) + { + Utils.ComponentIterator(c, + component => + { + switch (component) { - switch (component) - { - case TextBoxBase: - case ListControl: - break; - case Control control: - control.Text = Translate(control.Text); - break; - case ToolStripItem toolStripItem: - toolStripItem.Text = Translate(toolStripItem.Text); - break; - case ColumnHeader columnHeader: - columnHeader.Text = Translate(columnHeader.Text); - break; - } - }); - } + case TextBoxBase: + case ListControl: + break; + case Control control: + control.Text = Translate(control.Text); + break; + case ToolStripItem toolStripItem: + toolStripItem.Text = Translate(toolStripItem.Text); + break; + case ColumnHeader columnHeader: + columnHeader.Text = Translate(columnHeader.Text); + break; + } + }); } } \ No newline at end of file diff --git a/UnitTest/UnitTest.csproj b/UnitTest/UnitTest.csproj index 55ce09ec..adb6cd76 100644 --- a/UnitTest/UnitTest.csproj +++ b/UnitTest/UnitTest.csproj @@ -1,5 +1,5 @@ - + diff --git a/UnitTest/UnitTest1.cs b/UnitTest/UnitTest1.cs index 331183eb..185fe1dd 100644 --- a/UnitTest/UnitTest1.cs +++ b/UnitTest/UnitTest1.cs @@ -1,13 +1,12 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace UnitTest +namespace UnitTest; + +[TestClass] +public class UnitTest1 { - [TestClass] - public class UnitTest1 + [TestMethod] + public void TestMethod1() { - [TestMethod] - public void TestMethod1() - { - } } } \ No newline at end of file