using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.Win32; using Netch.Controllers; using Netch.Forms.Mode; using Netch.Models; using Netch.Utils; namespace Netch.Forms { public partial class MainForm : Form { #region Start private readonly Dictionary _mainFormText = new(); private bool _comboBoxInitialized; private bool _textRecorded; public MainForm() { InitializeComponent(); AddAddServerToolStripMenuItems(); #region i18N Translations _mainFormText.Add(UninstallServiceToolStripMenuItem.Name, new[] {"Uninstall {0}", "NF Service"}); _mainFormText.Add(UninstallTapDriverToolStripMenuItem.Name, new[] {"Uninstall {0}", "TUN/TAP driver"}); #endregion // 监听电源事件 SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; ModeComboBox.KeyUp += (sender, args) => { switch (args.KeyData) { case Keys.Escape: { SelectLastMode(); return; } } }; CheckForIllegalCrossThreadCalls = false; } private void MainForm_Load(object sender, EventArgs e) { OnlyInstance.Called += OnCalled; // 计算 ComboBox绘制 目标宽度 _comboBoxNumberBoxWidth = ServerComboBox.Width / 10; InitServer(); ServerHelper.DelayTestHelper.UpdateInterval(); ModeHelper.Load(); InitMode(); _comboBoxInitialized = true; // 加载翻译 InitText(); // 隐藏 NatTypeStatusLabel NatTypeStatusText(); _configurationGroupBoxHeight = ConfigurationGroupBox.Height; _profileConfigurationHeight = ConfigurationGroupBox.Controls[0].Height / 3; // 因为 AutoSize, 所以得到的是Controls的总高度 // 加载快速配置 InitProfile(); // 打开软件时启动加速,产生开始按钮点击事件 if (Global.Settings.StartWhenOpened) ControlButton_Click(null, null); Task.Run(() => { // 检查更新 if (Global.Settings.CheckUpdateWhenOpened) CheckUpdate(); }); Task.Run(async () => { // 检查订阅更新 if (Global.Settings.UpdateServersWhenOpened) await UpdateServersFromSubscribe(Global.Settings.UseProxyToUpdateSubscription); }); } /// /// Init at /// private int _comboBoxNumberBoxWidth; private void AddAddServerToolStripMenuItems() { foreach (var serversUtil in ServerHelper.ServerUtils.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) }; _mainFormText.Add(control.Name, new[] {"Add [{0}] Server", fullName}); control.Click += AddServerToolStripMenuItem_Click; ServerToolStripMenuItem.DropDownItems.Add(control); } } private void InitText() { #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(values.First() as string, 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 void ImportServersFromClipboardToolStripMenuItem_Click(object sender, EventArgs e) { var texts = Clipboard.GetText(); if (!string.IsNullOrWhiteSpace(texts)) { var servers = ShareLink.ParseText(texts); Global.Settings.Server.AddRange(servers); NotifyTip(i18N.TranslateFormat("Import {0} server(s) form Clipboard", servers.Count)); InitServer(); Configuration.Save(); } } private void AddServerToolStripMenuItem_Click(object sender, EventArgs e) { var s = ((ToolStripMenuItem) sender).Text; var start = s.IndexOf("[", StringComparison.Ordinal) + 1; var end = s.IndexOf("]", start, StringComparison.Ordinal); var result = s.Substring(start, end - start); Hide(); ServerHelper.GetUtilByFullName(result).Create(); InitServer(); Configuration.Save(); Show(); } #endregion #region Mode private void CreateProcessModeToolStripButton_Click(object sender, EventArgs e) { Hide(); new Process().ShowDialog(); Show(); } private void ReloadModesToolStripMenuItem_Click(object sender, EventArgs e) { Enabled = false; try { ModeHelper.Load(); InitMode(); NotifyTip(i18N.Translate("Modes have been reload")); } catch (Exception) { // ignored } finally { Enabled = true; } } #endregion #region Subscription private void ManageSubscribeLinksToolStripMenuItem_Click(object sender, EventArgs e) { Hide(); new SubscribeForm().ShowDialog(); InitServer(); Show(); } private async void UpdateServersFromSubscribeLinksToolStripMenuItem_Click(object sender, EventArgs e) { Global.Settings.UseProxyToUpdateSubscription = false; await UpdateServersFromSubscribe(); } private async void UpdateServersFromSubscribeLinksWithProxyToolStripMenuItem_Click(object sender, EventArgs e) { Global.Settings.UseProxyToUpdateSubscription = true; await UpdateServersFromSubscribe(true); } private async Task UpdateServersFromSubscribe(bool useProxy = false) { void DisableItems(bool v) { MenuStrip.Enabled = ConfigurationGroupBox.Enabled = ProfileGroupBox.Enabled = ControlButton.Enabled = v; } if (useProxy && ServerComboBox.SelectedIndex == -1) { MessageBoxX.Show(i18N.Translate("Please select a server first")); return; } if (Global.Settings.SubscribeLink.Count <= 0) { MessageBoxX.Show(i18N.Translate("No subscription link")); return; } StatusText(i18N.Translate("Starting update subscription")); DisableItems(false); try { string proxyServer = null; if (useProxy) { var mode = new Models.Mode { Remark = "ProxyUpdate", Type = 5 }; await MainController.Start(ServerComboBox.SelectedItem as Server, mode); proxyServer = $"http://127.0.0.1:{Global.Settings.HTTPLocalPort}"; } await Subscription.UpdateServersAsync(proxyServer); InitServer(); Configuration.Save(); StatusText(i18N.Translate("Subscription updated")); } catch (Exception) { // ignored } finally { if (useProxy) try { await MainController.Stop(); } catch { // ignored } DisableItems(true); } } #endregion #region Options private void CheckForUpdatesToolStripMenuItem_Click(object sender, EventArgs e) { Task.Run(() => { void OnNewVersionNotFound(object o, EventArgs args) { UpdateChecker.NewVersionNotFound -= OnNewVersionNotFound; NotifyTip(i18N.Translate("Already latest version")); } void OnNewVersionFoundFailed(object o, EventArgs args) { UpdateChecker.NewVersionFoundFailed -= OnNewVersionFoundFailed; NotifyTip(i18N.Translate("New version found failed"), info: false); } UpdateChecker.NewVersionNotFound += OnNewVersionNotFound; UpdateChecker.NewVersionFoundFailed += OnNewVersionFoundFailed; CheckUpdate(); }); } 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.FlushDNSResolverCache(); DNS.Cache.Clear(); }); NotifyTip(i18N.Translate("DNS cache cleanup succeeded")); } catch (Exception) { // ignored } finally { StatusText(); } } private void updateACLWithProxyToolStripMenuItem_Click(object sender, EventArgs e) { UpdateACL(true); } private void updateACLToolStripMenuItem_Click(object sender, EventArgs e) { UpdateACL(false); } private async void UpdateACL(bool useProxy) { if (useProxy && ServerComboBox.SelectedIndex == -1) { MessageBoxX.Show(i18N.Translate("Please select a server first")); return; } Enabled = false; StatusText(i18N.TranslateFormat("Updating {0}", "ACL")); try { if (useProxy) { var mode = new Models.Mode { Remark = "ProxyUpdate", Type = 5 }; State = State.Starting; await MainController.Start(ServerComboBox.SelectedItem as Server, mode); } var req = WebUtil.CreateRequest(Global.Settings.ACL); if (useProxy) req.Proxy = new WebProxy($"http://127.0.0.1:{Global.Settings.HTTPLocalPort}"); await WebUtil.DownloadFileAsync(req, Path.Combine(Global.NetchDir, "bin\\default.acl")); NotifyTip(i18N.Translate("ACL updated successfully")); } catch (Exception e) { NotifyTip(i18N.Translate("ACL update failed") + "\n" + e.Message, info: false); Logging.Error("更新 ACL 失败!" + e); } finally { if (useProxy) { await MainController.Stop(); State = State.Stopped; } StatusText(); Enabled = true; } } private async void updatePACToolStripMenuItem_Click(object sender, EventArgs eventArgs) { Enabled = false; StatusText(i18N.TranslateFormat("Updating {0}", "PAC")); try { var req = WebUtil.CreateRequest(Global.Settings.PAC); var pac = Path.Combine(Global.NetchDir, "bin\\pac.txt"); await WebUtil.DownloadFileAsync(req, pac); NotifyTip(i18N.Translate("PAC updated successfully")); } catch (Exception e) { NotifyTip(i18N.Translate("PAC update failed") + "\n" + e.Message, info: false); Logging.Error("更新 PAC 失败!" + e); } finally { StatusText(); Enabled = true; } } private async void UninstallServiceToolStripMenuItem_Click(object sender, EventArgs e) { Enabled = false; StatusText(i18N.TranslateFormat("Uninstalling {0}", "NF Service")); try { await Task.Run(() => { if (NFController.UninstallDriver()) NotifyTip(i18N.TranslateFormat("{0} has been uninstalled", "NF Service")); }); } finally { StatusText(); Enabled = true; } } private async void UninstallTapDriverToolStripMenuItem_Click(object sender, EventArgs e) { Enabled = false; StatusText(i18N.TranslateFormat("Uninstalling {0}", "TUN/TAP driver")); try { await Task.Run(TUNTAP.deltapall); NotifyTip(i18N.TranslateFormat("{0} has been uninstalled", "TUN/TAP driver")); } catch (Exception exception) { Logging.Error($"卸载 TUN/TAP 适配器失败: {exception}"); } finally { StatusText(); Enabled = true; } } #endregion /// /// 菜单栏强制退出 /// private void exitToolStripMenuItem_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 void AboutToolStripButton_Click(object sender, EventArgs e) { Hide(); new AboutForm().ShowDialog(); Show(); } private void fAQToolStripMenuItem_Click(object sender, EventArgs e) { Utils.Utils.Open("https://netch.org/#/docs/zh-CN/faq"); } #endregion #region ControlButton private async void ControlButton_Click(object sender, EventArgs e) { if (!IsWaiting()) { // 停止 State = State.Stopping; await MainController.Stop(); State = State.Stopped; return; } Configuration.Save(); // 服务器、模式 需选择 if (!(ServerComboBox.SelectedItem is Server server)) { MessageBoxX.Show(i18N.Translate("Please select a server first")); return; } if (!(ModeComboBox.SelectedItem is Models.Mode mode)) { MessageBoxX.Show(i18N.Translate("Please select a mode first")); return; } // 清除模式搜索框文本选择 ModeComboBox.Select(0, 0); State = State.Starting; if (!await MainController.Start(server, mode)) { State = State.Stopped; StatusText(i18N.Translate("Start failed")); return; } State = State.Started; if (Global.Settings.MinimizeWhenStarted) Minimize(); // 自动检测延迟 _ = Task.Run(() => { while (State == State.Started) { bool StartedPingEnabled() { return Global.Settings.StartedPingInterval >= 0; } if (StartedPingEnabled()) { server.Test(); ServerComboBox.Refresh(); } if (StartedPingEnabled()) Thread.Sleep(Global.Settings.StartedPingInterval * 1000); else Thread.Sleep(5000); } }); } #endregion #region SettingsButton private void SettingsButton_Click(object sender, EventArgs e) { Hide(); new SettingForm().ShowDialog(); if (i18N.LangCode != Global.Settings.Language) { i18N.Load(Global.Settings.Language); InitText(); InitMode(); InitProfile(); } if (ServerHelper.DelayTestHelper.Interval != Global.Settings.DetectionTick) ServerHelper.DelayTestHelper.UpdateInterval(); if (ProfileButtons.Count != Global.Settings.ProfileCount) InitProfile(); Show(); } #endregion #region Server private void InitServer() { var comboBoxInitialized = _comboBoxInitialized; _comboBoxInitialized = false; ServerComboBox.Items.Clear(); ServerComboBox.Items.AddRange(Global.Settings.Server.ToArray()); SelectLastServer(); _comboBoxInitialized = comboBoxInitialized; } public 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_SelectedIndexChanged(object sender, EventArgs o) { if (!_comboBoxInitialized) return; Global.Settings.ServerComboBoxSelectedIndex = ServerComboBox.SelectedIndex; } private void EditServerPictureBox_Click(object sender, EventArgs e) { // 当前ServerComboBox中至少有一项 if (ServerComboBox.SelectedIndex == -1) { MessageBoxX.Show(i18N.Translate("Please select a server first")); return; } Hide(); var server = Global.Settings.Server[ServerComboBox.SelectedIndex]; ServerHelper.GetUtilByTypeName(server.Type).Edit(server); InitServer(); Configuration.Save(); Show(); } private void SpeedPictureBox_Click(object sender, EventArgs e) { Enabled = false; StatusText(i18N.Translate("Testing")); if (IsWaiting()) { ServerHelper.DelayTestHelper.TestDelayFinished += OnTestDelayFinished; _ = Task.Run(ServerHelper.DelayTestHelper.TestAllDelay); void OnTestDelayFinished(object o1, EventArgs e1) { Refresh(); NotifyTip(i18N.Translate("Test done")); ServerHelper.DelayTestHelper.TestDelayFinished -= OnTestDelayFinished; Enabled = true; StatusText(); } } else { (ServerComboBox.SelectedItem as Server)?.Test(); ServerComboBox.Refresh(); Enabled = true; StatusText(); } } private void CopyLinkPictureBox_Click(object sender, EventArgs e) { // 当前ServerComboBox中至少有一项 if (ServerComboBox.SelectedIndex == -1) { MessageBoxX.Show(i18N.Translate("Please select a server first")); return; } try { //听说巨硬BUG经常会炸,所以Catch一下 :D var server = (Server) ServerComboBox.SelectedItem; 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.SelectedIndex == -1) { MessageBoxX.Show(i18N.Translate("Please select a server first")); return; } Global.Settings.Server.Remove(ServerComboBox.SelectedItem as Server); InitServer(); } #endregion #region Mode public void InitMode() { var comboBoxInitialized = _comboBoxInitialized; _comboBoxInitialized = false; ModeComboBox.Items.Clear(); ModeComboBox.Items.AddRange(Global.Modes.ToArray()); ModeComboBox.Tag = null; SelectLastMode(); _comboBoxInitialized = comboBoxInitialized; } public 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_SelectedIndexChanged(object sender, EventArgs o) { if (!_comboBoxInitialized) return; try { Global.Settings.ModeComboBoxSelectedIndex = Global.Modes.IndexOf((Models.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 = (Models.Mode) ModeComboBox.SelectedItem; if (ModifierKeys == Keys.Control) { Utils.Utils.Open(ModeHelper.GetFullPath(mode.RelativePath)); return; } switch (mode.Type) { case 0: Hide(); new Process(mode).ShowDialog(); Show(); break; default: Utils.Utils.Open(ModeHelper.GetFullPath(mode.RelativePath)); 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; } ModeHelper.Delete((Models.Mode) ModeComboBox.SelectedItem); SelectLastMode(); } #endregion #region Profile private int _configurationGroupBoxHeight; private int _profileConfigurationHeight; private void InitProfile() { // Clear foreach (var button in ProfileButtons) button.Dispose(); ProfileButtons.Clear(); ProfileTable.ColumnStyles.Clear(); ProfileTable.RowStyles.Clear(); var numProfile = Global.Settings.ProfileCount; if (numProfile == 0) { // Hide Profile GroupBox, Change window size configLayoutPanel.RowStyles[2].SizeType = SizeType.Percent; configLayoutPanel.RowStyles[2].Height = 0; ProfileGroupBox.Visible = false; ConfigurationGroupBox.Size = new Size(ConfigurationGroupBox.Size.Width, _configurationGroupBoxHeight - _profileConfigurationHeight); } else { // Load Profiles ProfileTable.ColumnCount = numProfile; while (Global.Settings.Profiles.Count < numProfile) Global.Settings.Profiles.Add(new Profile()); for (var i = 0; i < numProfile; ++i) { var b = new Button(); b.Click += ProfileButton_Click; b.Dock = DockStyle.Fill; b.Text = !Global.Settings.Profiles[i].IsDummy ? Global.Settings.Profiles[i].ProfileName : i18N.Translate("None"); ProfileTable.Controls.Add(b, i, 0); ProfileButtons.Add(b); } // 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; ConfigurationGroupBox.Size = new Size(ConfigurationGroupBox.Size.Width, _configurationGroupBoxHeight); } } private void LoadProfile(int index) { var p = Global.Settings.Profiles[index]; ProfileNameText.Text = p.ProfileName; ModeComboBox.ResetCompletionList(); if (p.IsDummy) throw new Exception("Profile not found."); var server = ServerComboBox.Items.Cast().FirstOrDefault(s => s.Remark.Equals(p.ServerRemark)); var mode = ModeComboBox.Items.Cast().FirstOrDefault(m => m.Remark.Equals(p.ModeRemark)); if (server == null) throw new Exception("Server not found."); if (mode == null) throw new Exception("Mode not found."); ServerComboBox.SelectedItem = server; ModeComboBox.SelectedItem = mode; } private void SaveProfile(int index) { var selectedServer = (Server) ServerComboBox.SelectedItem; var selectedMode = (Models.Mode) ModeComboBox.SelectedItem; var name = ProfileNameText.Text; Global.Settings.Profiles[index] = new Profile(selectedServer, selectedMode, name); } private void RemoveProfile(int index) { Global.Settings.Profiles[index] = new Profile(); } private readonly List