using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Net.Sockets; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text.Json; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using IWshRuntimeLibrary; using MaterialDesignThemes.Wpf; using Microsoft.Win32; using NginxConfigParser; using OnaCore; using Sheas_Cealer.Consts; using Sheas_Cealer.Preses; using Sheas_Cealer.Utils; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; using File = System.IO.File; namespace Sheas_Cealer.Wins; public partial class MainWin : Window { private static MainPres? MainPres; private static readonly HttpClient MainClient = new(new HttpClientHandler() { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }); private static DispatcherTimer? HoldButtonTimer; private static readonly DispatcherTimer ProxyTimer = new() { Interval = TimeSpan.FromSeconds(0.1) }; private static readonly FileSystemWatcher CealingHostWatcher = new(Path.GetDirectoryName(MainConst.CealingHostPath)!, Path.GetFileName(MainConst.CealingHostPath)) { EnableRaisingEvents = true, NotifyFilter = NotifyFilters.LastWrite }; private static readonly FileSystemWatcher NginxConfWatcher = new(Path.GetDirectoryName(MainConst.NginxConfPath)!, Path.GetFileName(MainConst.NginxConfPath)) { EnableRaisingEvents = true, NotifyFilter = NotifyFilters.LastWrite }; private static readonly FileSystemWatcher MihomoConfWatcher = new(Path.GetDirectoryName(MainConst.MihomoConfPath)!, Path.GetFileName(MainConst.MihomoConfPath)) { EnableRaisingEvents = true, NotifyFilter = NotifyFilters.LastWrite }; private static readonly Dictionary hostDomainPairs, string hostSni, string hostIp)>> HostRulesDict = []; private static string CealArgs = string.Empty; private static NginxConfig? NginxConfs; private static string? ExtraNginxConfs; private static string? MihomoConfs; private static string? ExtraMihomoConfs; private static int GameClickTime = 0; private static int GameFlashInterval = 1000; internal MainWin(string[] args) { InitializeComponent(); DataContext = MainPres = new(args); ProxyTimer.Tick += ProxyTimer_Tick; CealingHostWatcher.Changed += CealingHostWatcher_Changed; NginxConfWatcher.Changed += NginxConfWatcher_Changed; MihomoConfWatcher.Changed += MihomoConfWatcher_Changed; } protected override void OnSourceInitialized(EventArgs e) => IconRemover.RemoveIcon(this); private async void MainWin_Loaded(object sender, RoutedEventArgs e) { SettingsBox.Focus(); await Task.Run(() => { ProxyTimer.Start(); foreach (string hostPath in Directory.GetFiles(CealingHostWatcher.Path, CealingHostWatcher.Filter)) CealingHostWatcher_Changed(null!, new(new(), Path.GetDirectoryName(hostPath)!, Path.GetFileName(hostPath))); MihomoConfWatcher_Changed(null!, null!); }); } private void MainWin_Closing(object sender, CancelEventArgs e) => Application.Current.Shutdown(); private void MainWin_DragEnter(object sender, DragEventArgs e) { e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Link : DragDropEffects.None; e.Handled = true; } private void MainWin_Drop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) MainPres!.BrowserPath = ((string[])e.Data.GetData(DataFormats.FileDrop))[0]; } private void SettingsBox_TextChanged(object sender, TextChangedEventArgs e) { switch (MainPres!.SettingsMode) { case MainConst.SettingsMode.BrowserPathMode: MainPres.BrowserPath = SettingsBox.Text; return; case MainConst.SettingsMode.UpstreamUrlMode: MainPres.UpstreamUrl = SettingsBox.Text; return; case MainConst.SettingsMode.ExtraArgsMode: MainPres.ExtraArgs = SettingsBox.Text; return; } } private void SettingsModeButton_Click(object sender, RoutedEventArgs e) { MainPres!.SettingsMode = MainPres.SettingsMode switch { MainConst.SettingsMode.BrowserPathMode => MainConst.SettingsMode.UpstreamUrlMode, MainConst.SettingsMode.UpstreamUrlMode => MainConst.SettingsMode.ExtraArgsMode, MainConst.SettingsMode.ExtraArgsMode => MainConst.SettingsMode.BrowserPathMode, _ => throw new UnreachableException() }; } private void SettingsFunctionButton_Click(object sender, RoutedEventArgs e) { OpenFileDialog browserPathDialog = new() { Filter = $"{MainConst._BrowserPathDialogFilterFileType} (*.exe)|*.exe" }; switch (MainPres!.SettingsMode) { case MainConst.SettingsMode.BrowserPathMode when browserPathDialog.ShowDialog().GetValueOrDefault(): SettingsBox.Focus(); MainPres.BrowserPath = browserPathDialog.FileName; return; case MainConst.SettingsMode.UpstreamUrlMode: MainPres.UpstreamUrl = MainConst.DefaultUpstreamUrl; return; case MainConst.SettingsMode.ExtraArgsMode: MainPres.ExtraArgs = string.Empty; return; } } private void StartButton_Click(object sender, RoutedEventArgs e) { if (HoldButtonTimer == null || HoldButtonTimer.IsEnabled) StartButtonHoldTimer_Tick(null, null!); } private void StartButton_PreviewMouseDown(object sender, MouseButtonEventArgs e) { HoldButtonTimer = new() { Interval = TimeSpan.FromSeconds(1) }; HoldButtonTimer.Tick += StartButtonHoldTimer_Tick; HoldButtonTimer.Start(); } private async void StartButtonHoldTimer_Tick(object? sender, EventArgs e) { HoldButtonTimer?.Stop(); if (string.IsNullOrWhiteSpace(CealArgs)) throw new Exception(MainConst._HostErrorMsg); if (MessageBox.Show(MainConst._KillBrowserProcessPrompt, string.Empty, MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; IWshShortcut uncealedBrowserShortcut = (IWshShortcut)new WshShell().CreateShortcut(MainConst.UncealedBrowserPath); uncealedBrowserShortcut.TargetPath = MainPres!.BrowserPath; uncealedBrowserShortcut.Description = MainConst.UncealedBrowserDescription; uncealedBrowserShortcut.Save(); foreach (Process browserProcess in Process.GetProcessesByName(Path.GetFileNameWithoutExtension(MainPres.BrowserPath))) { browserProcess.Kill(); browserProcess.WaitForExit(); } await Task.Run(() => { new CommandProc(sender == null).ShellRun(AppDomain.CurrentDomain.SetupInformation.ApplicationBase!, ($"{CealArgs} {MainPres!.ExtraArgs}").Trim()); }); } private void NginxButton_Click(object sender, RoutedEventArgs e) { if (HoldButtonTimer == null || HoldButtonTimer.IsEnabled) NginxButtonHoldTimer_Tick(null, null!); } private void NginxButton_PreviewMouseDown(object sender, MouseButtonEventArgs e) { HoldButtonTimer = new() { Interval = TimeSpan.FromSeconds(1) }; HoldButtonTimer.Tick += NginxButtonHoldTimer_Tick; HoldButtonTimer.Start(); } private async void NginxButtonHoldTimer_Tick(object? sender, EventArgs e) { HoldButtonTimer?.Stop(); if (!MainPres!.IsNginxRunning) { if (MessageBox.Show(MainConst._LaunchProxyPrompt, string.Empty, MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; if (!File.Exists(MainConst.NginxConfPath)) File.Create(MainConst.NginxConfPath).Dispose(); if (!Directory.Exists(MainConst.NginxLogsPath)) Directory.CreateDirectory(MainConst.NginxLogsPath); if (!Directory.Exists(MainConst.NginxTempPath)) Directory.CreateDirectory(MainConst.NginxTempPath); RSA certKey = RSA.Create(2048); CertificateRequest rootCertRequest = new(MainConst.NginxRootCertSubjectName, certKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); rootCertRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, false)); X509Certificate2 rootCert = rootCertRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(100)); using X509Store certStore = new(StoreName.Root, StoreLocation.CurrentUser, OpenFlags.ReadWrite); certStore.Add(rootCert); certStore.Close(); CertificateRequest childCertRequest = new(MainConst.NginxChildCertSubjectName, certKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); SubjectAlternativeNameBuilder childCertSanBuilder = new(); string hostsConfAppendContent = MainConst.HostsConfStartMarker; foreach (List<(List<(string hostIncludeDomain, string hostExcludeDomain)> hostDomainPairs, string hostSni, string hostIp)> hostRules in HostRulesDict.Values) foreach ((List<(string hostIncludeDomain, string hostExcludeDomain)> hostDomainPairs, _, _) in hostRules) foreach ((string hostIncludeDomain, _) in hostDomainPairs) { // 配置证书 SAN if (hostIncludeDomain.StartsWith("*.")) { childCertSanBuilder.AddDnsName("*" + hostIncludeDomain.Replace("*", string.Empty)); continue; } else if (hostIncludeDomain.StartsWith('*')) childCertSanBuilder.AddDnsName("*." + hostIncludeDomain.Replace("*", string.Empty)); childCertSanBuilder.AddDnsName(hostIncludeDomain.Replace("*", string.Empty)); // 配置 hosts string hostIncludeDomainWithoutWildcard = hostIncludeDomain.Replace("*", string.Empty); if (hostIncludeDomainWithoutWildcard.StartsWith('^') || hostIncludeDomainWithoutWildcard.EndsWith('^') || hostIncludeDomainWithoutWildcard.StartsWith('.') || hostIncludeDomainWithoutWildcard.EndsWith('.')) continue; hostsConfAppendContent += $"127.0.0.1 {hostIncludeDomainWithoutWildcard.Split('^', 2)[0]}\n"; if (hostIncludeDomain.StartsWith('*')) hostsConfAppendContent += $"127.0.0.1 www.{hostIncludeDomainWithoutWildcard.Split('^', 2)[0]}\n"; } childCertRequest.CertificateExtensions.Add(childCertSanBuilder.Build()); X509Certificate2 childCert = childCertRequest.Create(rootCert, rootCert.NotBefore, rootCert.NotAfter, Guid.NewGuid().ToByteArray()); File.WriteAllText(MainConst.NginxCertPath, childCert.ExportCertificatePem()); File.WriteAllText(MainConst.NginxKeyPath, certKey.ExportPkcs8PrivateKeyPem()); hostsConfAppendContent += MainConst.HostsConfEndMarker; File.AppendAllText(MainConst.HostsConfPath, hostsConfAppendContent); MainPres.IsNginxIniting = true; NginxConfWatcher.EnableRaisingEvents = false; NginxConfs!.Save(MainConst.NginxConfPath); await Task.Run(() => { new NginxProc().ShellRun(AppDomain.CurrentDomain.SetupInformation.ApplicationBase!, @$"-c ""{MainConst.NginxConfPath}"""); }); while (true) { try { await Http.GetAsync("https://localhost", MainClient); break; } catch (HttpRequestException ex) when (ex.InnerException is SocketException innerEx) { if (innerEx.SocketErrorCode != SocketError.ConnectionRefused) break; } if (!MainPres.IsNginxRunning) break; } File.WriteAllText(MainConst.NginxConfPath, ExtraNginxConfs); NginxConfWatcher.EnableRaisingEvents = true; MainPres.IsNginxIniting = false; if (sender == null) Application.Current.Dispatcher.InvokeShutdown(); } else foreach (Process nginxProcess in Process.GetProcessesByName(Path.GetFileNameWithoutExtension(MainConst.NginxPath))) { nginxProcess.Kill(); nginxProcess.WaitForExit(); } } private void MihomoButton_Click(object sender, RoutedEventArgs e) { if (HoldButtonTimer == null || HoldButtonTimer.IsEnabled) MihomoButtonHoldTimer_Tick(null, null!); } private void MihomoButton_PreviewMouseDown(object sender, MouseButtonEventArgs e) { HoldButtonTimer = new() { Interval = TimeSpan.FromSeconds(1) }; HoldButtonTimer.Tick += MihomoButtonHoldTimer_Tick; HoldButtonTimer.Start(); } private async void MihomoButtonHoldTimer_Tick(object? sender, EventArgs e) { HoldButtonTimer?.Stop(); if (!MainPres!.IsMihomoRunning) { if (MessageBox.Show(MainConst._LaunchProxyPrompt, string.Empty, MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; if (!File.Exists(MainConst.MihomoConfPath)) File.Create(MainConst.MihomoConfPath).Dispose(); MainPres.IsMihomoIniting = true; MihomoConfWatcher.EnableRaisingEvents = false; File.WriteAllText(MainConst.MihomoConfPath, MihomoConfs); await Task.Run(() => { new MihomoProc().ShellRun(AppDomain.CurrentDomain.SetupInformation.ApplicationBase!, @$"-d ""{Path.GetDirectoryName(MainConst.MihomoConfPath)}"""); }); while (true) { try { await Http.GetAsync("http://localhost:7880", MainClient); break; } catch (HttpRequestException ex) when (ex.InnerException is SocketException innerEx) { if (innerEx.SocketErrorCode != SocketError.ConnectionRefused) break; } if (!MainPres.IsMihomoRunning) break; } File.WriteAllText(MainConst.MihomoConfPath, ExtraMihomoConfs); MihomoConfWatcher.EnableRaisingEvents = true; MainPres.IsMihomoIniting = false; if (sender == null) Application.Current.Dispatcher.InvokeShutdown(); } else foreach (Process mihomoProcess in Process.GetProcessesByName("Cealing-Mihomo")) { mihomoProcess.Kill(); mihomoProcess.WaitForExit(); } } private void EditHostButton_Click(object sender, RoutedEventArgs e) { Button? senderButton = sender as Button; string hostPath = senderButton == EditLocalHostButton ? MainConst.LocalHostPath : MainConst.UpstreamHostPath; if (!File.Exists(hostPath)) File.Create(hostPath).Dispose(); ProcessStartInfo processStartInfo = new(hostPath) { UseShellExecute = true }; Process.Start(processStartInfo); } private void EditConfButton_Click(object sender, RoutedEventArgs e) { Button? senderButton = sender as Button; string confPath = senderButton == EditHostsConfButton ? MainConst.HostsConfPath : senderButton == EditNginxConfButton ? MainConst.NginxConfPath : MainConst.MihomoConfPath; if (!File.Exists(confPath)) File.Create(confPath).Dispose(); ProcessStartInfo processStartInfo = new(confPath) { UseShellExecute = true }; Process.Start(processStartInfo); } private async void UpdateUpstreamHostButton_Click(object sender, RoutedEventArgs e) { string upstreamUpstreamHostUrl = (MainPres!.UpstreamUrl.StartsWith("http://") || MainPres!.UpstreamUrl.StartsWith("https://") ? string.Empty : "https://") + MainPres!.UpstreamUrl; string upstreamUpstreamHostString = await Http.GetAsync(upstreamUpstreamHostUrl, MainClient); string localUpstreamHostString; if (!File.Exists(MainConst.UpstreamHostPath)) File.Create(MainConst.UpstreamHostPath).Dispose(); localUpstreamHostString = File.ReadAllText(MainConst.UpstreamHostPath); if (localUpstreamHostString.Replace("\r", string.Empty) == upstreamUpstreamHostString) MessageBox.Show(MainConst._UpstreamHostUtdMsg); else { MessageBoxResult overrideOptionResult = MessageBox.Show(MainConst._OverrideUpstreamHostPrompt, string.Empty, MessageBoxButton.YesNoCancel); if (overrideOptionResult == MessageBoxResult.Yes) { File.WriteAllText(MainConst.UpstreamHostPath, upstreamUpstreamHostString); MessageBox.Show(MainConst._UpdateUpstreamHostSuccessMsg); } else if (overrideOptionResult == MessageBoxResult.No) Process.Start(new ProcessStartInfo(upstreamUpstreamHostUrl) { UseShellExecute = true }); } } private void ThemesButton_Click(object sender, RoutedEventArgs e) => MainPres!.IsLightTheme = MainPres.IsLightTheme.HasValue ? MainPres.IsLightTheme.Value ? null : true : false; private async void NoClickButton_Click(object sender, RoutedEventArgs e) { if (GameFlashInterval <= 10) { MessageBox.Show(MainConst._GameReviewEndingMsg); return; } ++GameClickTime; switch (GameClickTime) { case 1: MessageBox.Show(MainConst._GameClickOnceMsg); return; case 2: MessageBox.Show(MainConst._GameClickTwiceMsg); return; case 3: MessageBox.Show(MainConst._GameClickThreeMsg); return; } if (!MainPres!.IsFlashing) { MessageBox.Show(MainConst._GameStartMsg); MainPres.IsFlashing = true; Random random = new(); while (GameFlashInterval > 10) { Left = random.Next(0, (int)(SystemParameters.PrimaryScreenWidth - ActualWidth)); Top = random.Next(0, (int)(SystemParameters.PrimaryScreenHeight - ActualHeight)); PaletteHelper paletteHelper = new(); Theme newTheme = paletteHelper.GetTheme(); newTheme.SetPrimaryColor(Color.FromRgb((byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(256))); newTheme.SetBaseTheme(random.Next(2) == 0 ? BaseTheme.Light : BaseTheme.Dark); paletteHelper.SetTheme(newTheme); if (GameFlashInterval > 100) GameFlashInterval += random.Next(1, 4); await Task.Delay(GameFlashInterval); } MainPres.IsFlashing = false; MessageBox.Show(MainConst._GameEndingMsg); } else { switch (GameFlashInterval) { case > 250: GameFlashInterval -= 150; break; case > 100: GameFlashInterval = 100; break; case > 10: GameFlashInterval -= 30; break; } if (GameFlashInterval > 10) MessageBox.Show($"{MainConst._GameGradeMsg} {GameFlashInterval}"); } } private void AboutButton_Click(object sender, RoutedEventArgs e) => new AboutWin().ShowDialog(); private void ProxyTimer_Tick(object? sender, EventArgs e) { MainPres!.IsNginxExist = File.Exists(Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase!, Path.GetFileName(MainConst.NginxPath))); MainPres.IsNginxRunning = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(MainConst.NginxPath)).Length != 0; MainPres.IsMihomoExist = File.Exists(Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase!, Path.GetFileName(MainConst.MihomoPath))); MainPres.IsMihomoRunning = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(MainConst.MihomoPath)).Length != 0; } private void CealingHostWatcher_Changed(object sender, FileSystemEventArgs e) { string hostName = e.Name!.TrimStart("Cealing-Host-".ToCharArray()).TrimEnd(".json".ToCharArray()); try { HostRulesDict[hostName] = []; string hostRulesFragments = string.Empty; string hostResolverRulesFragments = string.Empty; using FileStream hostStream = new(e.FullPath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); JsonDocumentOptions hostOptions = new() { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip }; JsonElement hostArray = JsonDocument.Parse(hostStream, hostOptions).RootElement; foreach (JsonElement hostRule in hostArray.EnumerateArray()) { List<(string hostIncludeDomain, string hostExcludeDomain)> hostDomainPairs = []; string hostSni = string.IsNullOrWhiteSpace(hostRule[1].ToString()) ? $"{hostName}{HostRulesDict[hostName].Count}" : hostRule[1].ToString(); string hostIp = string.IsNullOrWhiteSpace(hostRule[2].ToString()) ? "127.0.0.1" : hostRule[2].ToString(); foreach (JsonElement hostDomain in hostRule[0].EnumerateArray()) { if (hostDomain.ToString().StartsWith('^') || hostDomain.ToString().EndsWith('^')) continue; string[] hostDomainPair = hostDomain.ToString().Split('^', 2); hostDomainPairs.Add((hostDomainPair[0], hostDomainPair.Length == 2 ? hostDomainPair[1] : string.Empty)); } HostRulesDict[hostName].Add((hostDomainPairs, hostSni, hostIp)); } } catch { HostRulesDict.Remove(hostName); } finally { string cealHostRules = string.Empty; string cealHostResolverRules = string.Empty; foreach (List<(List<(string hostIncludeDomain, string hostExcludeDomain)> hostDomainPairs, string hostSni, string hostIp)> hostRules in HostRulesDict.Values) foreach ((List<(string hostIncludeDomain, string hostExcludeDomain)> hostDomainPairs, string hostSni, string hostIp) in hostRules) { foreach ((string hostIncludeDomain, string hostExcludeDomain) in hostDomainPairs) cealHostRules += $"MAP {hostIncludeDomain} {hostSni}," + (!string.IsNullOrWhiteSpace(hostExcludeDomain) ? $"EXCLUDE {hostExcludeDomain}," : string.Empty); cealHostResolverRules += $"MAP {hostSni} {hostIp},"; } CealArgs = @$"/c @start .\""{Path.GetFileName(MainConst.UncealedBrowserPath)}"" --host-rules=""{cealHostRules.TrimEnd(',')}"" --host-resolver-rules=""{cealHostResolverRules.TrimEnd(',')}"" --test-type --ignore-certificate-errors"; NginxConfWatcher_Changed(null!, null!); } } private void NginxConfWatcher_Changed(object sender, FileSystemEventArgs e) { if (MainConst.IsAdmin && MainPres!.IsNginxExist) { if (!File.Exists(MainConst.NginxConfPath)) File.Create(MainConst.NginxConfPath).Dispose(); if (!Directory.Exists(MainConst.NginxLogsPath)) Directory.CreateDirectory(MainConst.NginxLogsPath); if (!Directory.Exists(MainConst.NginxTempPath)) Directory.CreateDirectory(MainConst.NginxTempPath); ExtraNginxConfs = File.ReadAllText(MainConst.NginxConfPath); int ruleIndex = 1; NginxConfs = NginxConfig.Load(ExtraNginxConfs) .AddOrUpdate("worker_processes", "auto") .AddOrUpdate("events:worker_connections", "65536") .AddOrUpdate("http:proxy_set_header", "Host $http_host") .AddOrUpdate("http:proxy_ssl_server_name", "on") .AddOrUpdate("http:server:return", "https://$host$request_uri"); foreach (List<(List<(string hostIncludeDomain, string hostExcludeDomain)> hostDomainPairs, string hostSni, string hostIp)> hostRules in HostRulesDict.Values) foreach ((List<(string hostIncludeDomain, string hostExcludeDomain)> hostDomainPairs, string hostSni, string hostIp) in hostRules) { string serverName = "~"; foreach ((string hostIncludeDomain, string hostExcludeDomain) in hostDomainPairs) serverName += "^" + (!string.IsNullOrWhiteSpace(hostExcludeDomain) ? $"(?!{hostExcludeDomain.Replace(".", "\\.").Replace("*", ".*")})" : string.Empty) + hostIncludeDomain.Replace(".", "\\.").Replace("*", ".*") + "$|"; NginxConfs = NginxConfs .AddOrUpdate($"http:server[{ruleIndex}]:server_name", serverName.TrimEnd('|')) .AddOrUpdate($"http:server[{ruleIndex}]:listen", "443 ssl") .AddOrUpdate($"http:server[{ruleIndex}]:ssl_certificate", Path.GetFileName(MainConst.NginxCertPath)) .AddOrUpdate($"http:server[{ruleIndex}]:ssl_certificate_key", Path.GetFileName(MainConst.NginxKeyPath)) .AddOrUpdate($"http:server[{ruleIndex}]:proxy_ssl_name", hostSni) .AddOrUpdate($"http:server[{ruleIndex}]:location", "/", true) .AddOrUpdate($"http:server[{ruleIndex}]:location:proxy_pass", $"https://{hostIp}"); ++ruleIndex; } } } private void MihomoConfWatcher_Changed(object sender, FileSystemEventArgs e) { if (MainConst.IsAdmin && MainPres!.IsMihomoExist) { if (!File.Exists(MainConst.MihomoConfPath)) File.Create(MainConst.MihomoConfPath).Dispose(); ExtraMihomoConfs = File.ReadAllText(MainConst.MihomoConfPath); Dictionary mihomoConfDict = new DeserializerBuilder() .WithNamingConvention(HyphenatedNamingConvention.Instance) .IgnoreUnmatchedProperties() .Build() .Deserialize>(ExtraMihomoConfs) ?? []; mihomoConfDict["mixed-port"] = 7880; mihomoConfDict["dns"] = new { enable = true, listen = ":53", enhancedMode = "redir-host", nameserver = new[] { "https://doh.apad.pro/dns-query", "https://ns.net.kg/dns-query" } }; mihomoConfDict["tun"] = new { enable = true, stack = "system", autoRoute = true, autoDetectInterface = true, dnsHijack = new[] { "any:53", "tcp://any:53" } }; MihomoConfs = new SerializerBuilder().WithNamingConvention(HyphenatedNamingConvention.Instance).Build().Serialize(mihomoConfDict); } } private void MainWin_KeyDown(object sender, KeyEventArgs e) { if (e.KeyboardDevice.Modifiers == ModifierKeys.Control && e.Key == Key.W) Application.Current.Shutdown(); } }