-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathMainWindow.xaml.cs
1866 lines (1628 loc) · 86.7 KB
/
MainWindow.xaml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Microsoft.Win32;
using Microsoft.Win32.TaskScheduler;
using Newtonsoft.Json.Linq;
using Hardcodet.Wpf.TaskbarNotification;
using SNIBypassGUI.Models;
using static SNIBypassGUI.Utils.LogManager;
using static SNIBypassGUI.Utils.ConvertUtils;
using static SNIBypassGUI.Utils.ConvertUtils.FileSizeConverter;
using static SNIBypassGUI.Utils.FileUtils;
using static SNIBypassGUI.Utils.WinApiUtils;
using static SNIBypassGUI.Utils.NetworkUtils;
using static SNIBypassGUI.Utils.IniFileUtils;
using static SNIBypassGUI.Utils.GitHubUtils;
using static SNIBypassGUI.Utils.ProcessUtils;
using static SNIBypassGUI.Utils.CertificateUtils;
using static SNIBypassGUI.Utils.AcrylicServiceUtils;
using static SNIBypassGUI.Utils.ServiceUtils;
using static SNIBypassGUI.Consts.AppConsts;
using static SNIBypassGUI.Consts.PathConsts;
using static SNIBypassGUI.Consts.CollectionConsts;
using static SNIBypassGUI.Consts.LinksConsts;
using static SNIBypassGUI.Utils.NetworkAdapterUtils;
using Task = System.Threading.Tasks.Task;
using Action = System.Action;
using MessageBox = HandyControl.Controls.MessageBox;
namespace SNIBypassGUI
{
public partial class MainWindow : Window
{
public ICommand TaskbarIconLeftClickCommand { get; }
private readonly DispatcherTimer serviceStatusUpdateTimer = new() { Interval = TimeSpan.FromSeconds(3) };
private readonly DispatcherTimer adaptersComboUpdateTimer = new() { Interval = TimeSpan.FromSeconds(5) };
private readonly DispatcherTimer tempFilesSizeUpdateTimer = new() { Interval = TimeSpan.FromSeconds(5) };
private readonly DispatcherTimer controlsStatusUpdateTimer = new() { Interval = TimeSpan.FromSeconds(5) };
/// <summary>
/// 窗口 构造函数
/// </summary>
public MainWindow()
{
if(StringToBool(INIRead("高级设置", "GUIDebug", INIPath))) EnableLog();
WriteLog("进入 MainWindow 。", LogLevel.Debug);
InitializeComponent();
// 检查是否已经开启程序,如果已开启则退出
if (GetProcessCount(Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.ModuleName)) > 1)
{
WriteLog("检测到程序已经在运行,将退出程序。", LogLevel.Warning);
MessageBox.Show("SNIBypassGUI 已经在运行!\r\n请检查是否有托盘图标!(((゚Д゚;)))", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
Environment.Exit(1);
return;
}
// 将 MainWindow 作为 DataContext 设置
DataContext = this;
TaskbarIconLeftClickCommand = new RelayCommand(() => TaskbarIcon_LeftClick());
// 窗口可拖动
TopBar.MouseLeftButtonDown += (o, e) => { DragMove(); };
// 刷新托盘图标,避免有多个托盘图标出现
RefreshNotification();
WriteLog("完成 MainWindow 。", LogLevel.Debug);
}
/// <summary>
/// 将代理开关项逐个添加到列表
/// </summary>
private void AddSwitchesToList()
{
WriteLog("进入 AddSwitchItems 。", LogLevel.Debug);
// 索引,用于确定每个代理开关项的位置
int ItemIndex = 0;
// 遍历代理开关项
foreach (SwitchItem item in Switchs)
{
// 创建站点图标
Image favicon = new()
{
Source = new BitmapImage(new Uri(item.FaviconImageSource, UriKind.RelativeOrAbsolute)),
Height = 32,
Width = 32,
Margin = new Thickness(10, 10, 10, 5)
};
// 设置站点图标的位置
Grid.SetColumn(favicon, 0);
Grid.SetRow(favicon, ItemIndex);
// 将站点图标添加到列表中
Switchlist.Children.Add(favicon);
// 创建站点名称及链接
TextBlock textBlock = new()
{
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(5, 3, 10, 3)
};
// 添加站点名称
textBlock.Inlines.Add(new Run { Text = item.SwitchTitle, FontSize = 16 });
// 添加换行,使链接换行显示
textBlock.Inlines.Add(new LineBreak());
// 添加站点链接
foreach (var linksTextParts in item.LinksText)
{
if (linksTextParts == "、" || linksTextParts == "等") textBlock.Inlines.Add(new Run { Text = linksTextParts, FontSize = 15, FontWeight = FontWeights.Bold });
else
{
Run run = new() { Text = linksTextParts, Foreground = new SolidColorBrush(Color.FromArgb(0xFF, 0x00, 0xF9, 0xFF)), FontSize = 15, Cursor = Cursors.Hand, FontFamily = new FontFamily("Microsoft Tai Le") };
run.PreviewMouseDown += LinkText_PreviewMouseDown;
textBlock.Inlines.Add(run);
}
}
// 设置站点名称及链接的位置
Grid.SetColumn(textBlock, 1);
Grid.SetRow(textBlock, ItemIndex);
// 将站点名称及链接添加到列表中
Switchlist.Children.Add(textBlock);
// 创建代理开关按钮
ToggleButton toggleButton = new()
{
Width = 40,
Margin = new Thickness(5, 0, 5, 0),
IsChecked = true,
Style = (Style)FindResource("ToggleButtonSwitch")
};
// 设置代理开关按钮的点击事件
toggleButton.Click += ToggleButtonsClick;
// 设置代理开关按钮的位置
Grid.SetColumn(toggleButton, 2);
Grid.SetRow(toggleButton, ItemIndex);
// 将代理开关按钮添加到列表中
Switchlist.Children.Add(toggleButton);
/*
* https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.frameworkelement.registername
*
* FrameworkElement.RegisterName(String, Object) 方法
*
* 定义:
* 命名空间: System.Windows
* 程序集: PresentationFramework.dll
*
* 提供一个可简化对 NameScope(https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.namescope) 注册方法访问的访问器。
* public void RegisterName (string name, object scopedElement);
*
* 参数:
* name: 要在指定的名称-对象映射中使用的名称。
* scopedElement: 映射的对象。
*
* 注解:
* 此方法是调用的 RegisterName(https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.namescope.registername)便利方法。
* 实现将检查连续的父元素,直到找到适用的 NameScope 实现,通过查找实现的 INameScope(https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.markup.inamescope)元素来找到该实现。
* 有关名称范围的详细信息,请参阅 WPF XAML 名称范围。调用 RegisterName 是必需的,以便在代码中创建时为应用程序正确挂钩动画情节提要。
* 这是因为其中一个关键情节提要属性 TargetName使用运行时名称查找,而不是能够引用目标元素。 即使该元素可通过代码引用访问,也是如此。
* 有关为何需要注册情节提要目标名称的详细信息,请参阅 情节提要概述。
*
*/
// 注册代理开关按钮的名称
Switchlist.RegisterName(item.ToggleButtonName, toggleButton);
// 为列表添加行
Switchlist.RowDefinitions.Add(new RowDefinition());
// 递增索引
ItemIndex++;
}
// 拓展首列与尾列的背景
Grid.SetRowSpan(FirstColumnBorder, ItemIndex);
Grid.SetRowSpan(LastColumnBorder, ItemIndex);
WriteLog("完成AddSwitchItems。", LogLevel.Debug);
}
/// <summary>
/// 更新临时文件大小
/// </summary>
private void UpdateTempFilesSize() => CleanBtn.Content = $"清理服务运行日志及缓存 ({ConvertBetweenUnits(GetTotalSize(TempFilesPathsIncludingGUILogs), SizeUnit.B, SizeUnit.MB).ToString("0.00")}MB)";
/// <summary>
/// 更新适配器列表
/// </summary>
private void UpdateAdaptersCombo()
{
WriteLog("进入 UpdateAdaptersCombo 。", LogLevel.Debug);
// 获取友好名称不为空的适配器
List<NetworkAdapter> adapters = GetNetworkAdapters(ScopeNeeded.FriendlyNameNotNullOnly);
// 清空下拉框避免重复添加
AdaptersCombo.Items.Clear();
// 将名字不为空的适配器逐个添加到下拉框
foreach (var adapter in adapters)
{
if (!string.IsNullOrEmpty(adapter.FriendlyName))
{
WriteLog($"向适配器列表添加 {adapter.FriendlyName} 。", LogLevel.Info);
AdaptersCombo.Items.Add(adapter.FriendlyName);
}
}
// 从配置文件中读取上次选中的适配器
string PreviousSelectedAdapter = INIRead("程序设置", "SpecifiedAdapter", INIPath);
// 如果更新后的列表包含之前选中的适配器,那么重新选中
// Cast<string>() 假定 AdaptersCombo.Items 中的所有项都是 string 类型。如果不是,可能会遇到运行时错误。
// 使用 OfType<string>() 来安全地处理不同类型的项。OfType<string>() 会自动跳过非字符串类型的项。
if (AdaptersCombo.Items.OfType<string>().Any(item => item == PreviousSelectedAdapter))
{
// SelectedItem 会确保 AdaptersCombo 正确选中与 PreviousSelectedAdapter 匹配的项。
// 使用 Text 设置文本会导致 AdaptersCombo 显示 PreviousSelectedAdapter,但它并不意味着该项被选中了(特别是当该项不在 Items 中时)。
AdaptersCombo.SelectedItem = PreviousSelectedAdapter;
}
else
{
WriteLog($"适配器列表中丢失 {PreviousSelectedAdapter} ,取消选中。", LogLevel.Warning);
// 如果没有匹配的项,取消选中
AdaptersCombo.SelectedItem = null;
}
WriteLog("完成 UpdateAdaptersCombo 。", LogLevel.Debug);
}
/// <summary>
/// 更新服务状态
/// </summary>
public void UpdateServiceStatus()
{
WriteLog("进入 UpdateServiceStatus 。", LogLevel.Debug);
// 检查主服务是否在运行
bool IsNginxRunning = IsProcessRunning(NginxProcessName);
// 检查DNS服务是否在运行
bool IsDnsRunning = IsAcrylicServiceRunning();
WriteLog($"主服务运行中: {BoolToYesNo(IsNginxRunning)}",LogLevel.Info);
WriteLog($"DNS服务运行中: {BoolToYesNo(IsDnsRunning)}",LogLevel.Info);
// 根据不同情况显示不同的服务状态文本
if (IsNginxRunning && IsDnsRunning)
{
// 主服务和DNS服务都在运行的情况
ServiceStatusText.Text = "当前服务状态:\r\n主服务和DNS服务运行中";
TaskbarIconServiceST.Text = "主服务和DNS服务运行中";
ServiceStatusText.Foreground = TaskbarIconServiceST.Foreground = new SolidColorBrush(Colors.ForestGreen);
AdaptersCombo.IsEnabled = GetActiveAdapterBtn.IsEnabled = false;
}
else if (IsNginxRunning)
{
// 仅主服务在运行的情况
ServiceStatusText.Text = "当前服务状态:\r\n主服务运行中,但DNS服务未运行";
TaskbarIconServiceST.Text = "仅主服务运行中";
ServiceStatusText.Foreground = TaskbarIconServiceST.Foreground = new SolidColorBrush(Colors.DarkOrange);
AdaptersCombo.IsEnabled = GetActiveAdapterBtn.IsEnabled = false;
}
else if (IsDnsRunning)
{
// 仅DNS服务在运行的情况
ServiceStatusText.Text = "当前服务状态:\r\n主服务未运行,但DNS服务运行中";
TaskbarIconServiceST.Text = "仅DNS服务运行中";
ServiceStatusText.Foreground = TaskbarIconServiceST.Foreground = new SolidColorBrush(Colors.DarkOrange);
AdaptersCombo.IsEnabled = GetActiveAdapterBtn.IsEnabled = false;
}
else
{
// 服务都不在运行的情况
ServiceStatusText.Text = "当前服务状态:\r\n主服务与DNS服务未运行";
TaskbarIconServiceST.Text = "主服务与DNS服务未运行";
ServiceStatusText.Foreground = TaskbarIconServiceST.Foreground = new SolidColorBrush(Colors.Red);
AdaptersCombo.IsEnabled = GetActiveAdapterBtn.IsEnabled = true;
}
WriteLog("完成 UpdateServiceStatus 。", LogLevel.Debug);
}
/// <summary>
/// 更新背景图片
/// </summary>
public void UpdateBackground()
{
WriteLog("进入 UpdateBackground 。", LogLevel.Debug);
// 设置图片源为默认背景图片并设置图片的拉伸模式为均匀填充,以适应背景区域
ImageBrush bg = new()
{
ImageSource = new BitmapImage(new Uri("pack://application:,,,/SNIBypassGUI;component/Resources/DefaultBkg.png")),
Stretch = Stretch.UniformToFill
};
if (INIRead("程序设置", "Background", INIPath) == "Custom")
{
// 程序设置中背景为自定义的情况
if (File.Exists(CustomBackground))
{
// 如果找到了背景图片,用资源释放型的读取来获取背景图片
bg.ImageSource = GetImage(CustomBackground);
WriteLog($"背景图片将设置为自定义: {CustomBackground} 。", LogLevel.Info);
}
else
{
// 如果没有找到背景图片的路径
WriteLog("背景图片设置为自定义但未在指定位置找到文件,或被删除?将恢复为默认。", LogLevel.Warning);
// 将配置设置回默认背景
INIWrite("程序设置", "Background", "Default", INIPath);
}
}
// 设置背景图片
MainPage.Background = bg;
WriteLog("完成 UpdateBackground 。", LogLevel.Debug);
}
/// <summary>
/// 检查必要目录、文件的存在性,并在缺失时创建或释放
/// </summary>
public void InitializeDirectoriesAndFiles()
{
WriteLog("进入 InitializeDirectoriesAndFiles 。", LogLevel.Debug);
// 确保必要目录存在
foreach (string directory in NeccesaryDirectories) EnsureDirectoryExists(directory);
// 释放相关文件
foreach (var pair in PathToResourceDic)
{
if (!File.Exists(pair.Key))
{
WriteLog($"文件 {pair.Key} 不存在,释放。", LogLevel.Info);
ExtractResourceToFile(pair.Value, pair.Key);
}
}
if (!File.Exists(INIPath))
{
// 如果配置文件不存在,则创建配置文件
WriteLog($"配置文件 {INIPath} 不存在,创建。", LogLevel.Info);
// 写入初始配置
foreach (var config in InitialConfigurations)
{
var sections = config.Key.Split(':');
if (sections.Length == 2) INIWrite(sections[0], sections[1], config.Value, INIPath);
}
// 写入初始代理开关配置
foreach (SwitchItem pair in Switchs) INIWrite("代理开关", pair.SectionName, "true", INIPath);
}
WriteLog("完成 InitializeDirectoriesAndFiles 。", LogLevel.Debug);
}
/// <summary>
/// 更新一言
/// </summary>
public async Task UpdateYiyan()
{
WriteLog("进入 UpdateYiyan 。", LogLevel.Debug);
try
{
string YiyanJson = await GetAsync("https://v1.hitokoto.cn/?c=d");
WriteLog($"获取到一言的数据为 {YiyanJson} 。", LogLevel.Debug);
// 提取一言文本、来源、作者
JObject repodata = JObject.Parse(YiyanJson);
string Hitokoto = repodata["hitokoto"].ToString();
string From = repodata["from"].ToString();
string FromWho = repodata["from_who"].ToString();
WriteLog($"解析到一言文本为 {Hitokoto} ,来源为 {From} ,作者为 {FromWho} 。", LogLevel.Info);
// 将一言与相关信息显示在托盘图标悬浮文本
TaskbarIconYiyan.Text = Hitokoto;
TaskbarIconYiyanFrom.Text = $"—— {FromWho}「{From}」";
}
catch (Exception ex)
{
WriteLog($"遇到异常,将设置为默认一言。", LogLevel.Error, ex);
// 设置为默认一言
TaskbarIconYiyan.Text = DefaultYiyan;
TaskbarIconYiyanFrom.Text = DefaultYiyanFrom;
}
WriteLog("完成 UpdateYiyan 。", LogLevel.Debug);
}
/// <summary>
/// 从配置文件向 Hosts 文件更新
/// </summary>
public void UpdateHostsFromConfig()
{
WriteLog("进入 UpdateHostsFromConfig 。", LogLevel.Debug);
// 根据域名解析模式判断要更新的文件
bool IsDnsService = INIRead("高级设置", "DomainNameResolutionMethod", INIPath) == "DnsService";
string FileShouldUpdate = IsDnsService ? AcrylicHostsPath : SystemHosts;
// 根据域名解析模式获取应该添加的条目数据
string CorrespondingHosts = IsDnsService ? "AcrylicHostsRecord" : "SystemHostsRecord";
WriteLog($"当前域名解析方法是否为DNS服务: {BoolToYesNo(IsDnsService)} ,将更新的文件为 {FileShouldUpdate} 。", LogLevel.Info);
try
{
// 移除所有条目部分,防止重复添加
RemoveHostsRecords();
// 遍历条目部分名称
foreach (SwitchItem pair in Switchs)
{
if (StringToBool(INIRead("代理开关", pair.SectionName, INIPath)) == true)
{
// 条目部分名称对应的开关是打开的情况
WriteLog($"{pair.SectionName} 的代理开关为开启,将添加记录。", LogLevel.Info);
// 添加该条目部分
AppendToFile(FileShouldUpdate, (string[])pair.GetType().GetProperty(CorrespondingHosts).GetValue(pair));
}
}
}
catch (UnauthorizedAccessException ex)
{
WriteLog($"对系统hosts的访问被拒绝!", LogLevel.Error, ex);
if (MessageBox.Show($"对系统hosts的访问被拒绝。\r\n{ex}\r\n点击“是”将为您展示有关帮助。", "错误", MessageBoxButton.YesNo, MessageBoxImage.Error) == MessageBoxResult.Yes) Process.Start(new ProcessStartInfo(当您遇到对系统hosts的访问被拒绝的提示时) { UseShellExecute = true });
}
catch (Exception ex)
{
WriteLog($"遇到异常。", LogLevel.Error, ex);
MessageBox.Show($"更新 Hosts 文件时遇到异常。\r\n{ex}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
WriteLog("完成UpdateHostsFromConfig。", LogLevel.Debug);
}
/// <summary>
/// 移除 Hosts 中全部有关记录
/// </summary>
public void RemoveHostsRecords()
{
WriteLog("进入RemoveHosts。", LogLevel.Debug);
// 根据域名解析模式判断要更新的文件
bool IsDnsService = INIRead("高级设置", "DomainNameResolutionMethod", INIPath) == "DnsService";
string FileShouldUpdate = IsDnsService ? AcrylicHostsPath : SystemHosts;
WriteLog($"当前域名解析方法是否为 DNS 服务: {BoolToYesNo(IsDnsService)} ,将更新的文件为 {FileShouldUpdate} 。", LogLevel.Info);
try
{
foreach (SwitchItem pair in Switchs)
{
WriteLog($"移除 {pair.SectionName} 的记录部分。", LogLevel.Info);
RemoveSection(FileShouldUpdate, pair.SectionName);
}
}
catch (UnauthorizedAccessException ex)
{
WriteLog($"对系统 Hosts 的访问被拒绝!", LogLevel.Error, ex);
if (MessageBox.Show($"对系统 Hosts 的访问被拒绝。\r\n{ex}\r\n点击“是”将为您展示有关帮助。", "错误", MessageBoxButton.YesNo, MessageBoxImage.Error) == MessageBoxResult.Yes) StartProcess(当您遇到对系统hosts的访问被拒绝的提示时,useShellExecute: true );
}
catch (Exception ex)
{
WriteLog($"遇到异常。", LogLevel.Error, ex);
MessageBox.Show($"更新 Hosts 文件时遇到异常。\r\n{ex}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
WriteLog("完成 RemoveHosts 。", LogLevel.Debug);
}
/// <summary>
/// 从配置文件同步有关控件
/// </summary>
public void SyncControlsFromConfig()
{
WriteLog("进入 SyncControlsFromConfig 。", LogLevel.Debug);
// 遍历所有代理开关项
foreach (SwitchItem pair in Switchs)
{
ToggleButton toggleButtonInstance = (ToggleButton)FindName(pair.ToggleButtonName);
bool isEnabled = StringToBool(INIRead("代理开关", pair.SectionName, INIPath));
if (toggleButtonInstance != null)
{
// 更新代理开关状态
toggleButtonInstance.IsChecked = isEnabled;
WriteLog($"开关 {toggleButtonInstance.Name} 从配置键 {pair.SectionName} 同步状态: {BoolToYesNo(isEnabled)} 。", LogLevel.Debug);
}
}
// 判断调试模式是否开启
bool isDebugModeOn = StringToBool(INIRead("高级设置", "DebugMode", INIPath));
// 更新调试有关按钮文本
DebugModeBtn.Content = isDebugModeOn ? "调试模式:\n开" : "调试模式:\n关";
GUIDebugBtn.Content = StringToBool(INIRead("高级设置", "GUIDebug", INIPath)) ? "GUI调试:\n开" : "GUI调试:\n关";
SwitchDomainNameResolutionMethodBtn.Content = INIRead("高级设置", "DomainNameResolutionMethod", INIPath) == "DnsService" ? "域名解析:\nDNS服务" : "域名解析:\n系统hosts";
AcrylicDebugBtn.Content = StringToBool(INIRead("高级设置", "AcrylicDebug", INIPath)) ? "DNS服务调试:\n开" : "DNS服务调试:\n关";
PixivIPPreferenceBtn.Content = StringToBool(INIRead("程序设置", "PixivIPPreference", INIPath)) ? "Pixiv IP优选:开" : "Pixiv IP优选:关";
// 根据调试模式是否开启决定有关按钮是否启用
SwitchDomainNameResolutionMethodBtn.IsEnabled = AcrylicDebugBtn.IsEnabled = GUIDebugBtn.IsEnabled = isDebugModeOn;
// 更新适配器列表及选中的适配器
UpdateAdaptersCombo();
WriteLog("完成 SyncControlsFromConfig 。", LogLevel.Debug);
}
/// <summary>
/// 从代理开关列表向配置文件同步
/// </summary>
public void UpdateConfigFromToggleButtons()
{
WriteLog("进入 UpdateConfigFromToggleButtons 。", LogLevel.Debug);
// 遍历所有代理开关
foreach (var pair in Switchs)
{
ToggleButton toggleButtonInstance = (ToggleButton)FindName(pair.ToggleButtonName);
if (toggleButtonInstance != null)
{
if (toggleButtonInstance.IsChecked == true) INIWrite("代理开关", pair.SectionName, "true", INIPath);
else INIWrite("代理开关", pair.SectionName, "false", INIPath);
WriteLog($"配置键 {pair.SectionName} 从开关 {toggleButtonInstance.Name} 同步状态: {BoolToYesNo(toggleButtonInstance.IsChecked)} 。", LogLevel.Debug);
}
}
WriteLog("完成 UpdateConfigFromToggleButtons 。", LogLevel.Debug);
}
/// <summary>
/// 设置指定网络适配器首选DNS为环回地址并记录先前的DNS服务器地址
/// </summary>
/// <param name="Adapter">指定的网络适配器</param>
public void SetLoopbackDNS(NetworkAdapter Adapter)
{
WriteLog("进入 SetLoopbackDNS 。", LogLevel.Debug);
try
{
if (Adapter.IPv4DNSServer.Length == 0 || Adapter.IPv4DNSServer[0] != "127.0.0.1")
{
// 指定适配器的首选DNS不是127.0.0.1的情况
WriteLog($"开始配置网络适配器: {Adapter.FriendlyName} 。", LogLevel.Info);
// 在设置DNS之前记录DNS服务器是否为自动获取
bool? isIPv4DNSAuto = Adapter.IsIPv4DNSAuto;
// 用于暂存DNS服务器地址
string PreviousDNS1 = string.Empty, PreviousDNS2 = string.Empty;
if (isIPv4DNSAuto != true)
{
// 遍历 DNS 地址并获取有效的 DNS
int validDnsCount = 0;
foreach (var dns in Adapter.IPv4DNSServer)
{
if (IsValidIPv4(dns) && dns != "127.0.0.1")
{
if (validDnsCount == 0) PreviousDNS1 = dns;
else if (validDnsCount == 1) PreviousDNS2 = dns;
validDnsCount++;
if (validDnsCount == 2) break;
}
}
}
// 将指定适配器的IPv4 DNS服务器设置为首选127.0.0.1
SetIPv4DNS(Adapter, ["127.0.0.1"]);
// 将指定适配器的IPv6 DNS服务器设置为首选::1
SetIPv6DNS(Adapter, ["::1"]);
// 刷新指定适配器的信息
Adapter = Refresh(Adapter);
WriteLog($"指定网络适配器是否为自动获取 DNS: {BoolToYesNo(isIPv4DNSAuto)}", LogLevel.Info);
WriteLog($"成功设置指定网络适配器的 IPv4 首选 DNS 为 {Adapter.IPv4DNSServer[0]} ,IPv6 首选 DNS 为 {Adapter.IPv6DNSServer[0]}", LogLevel.Info);
WriteLog($"将暂存的DNS服务器为: {PreviousDNS1} , {PreviousDNS2}", LogLevel.Debug);
// 将停止服务时恢复适配器所需要的信息写入配置文件备用
INIWrite("暂存数据", "PreviousDNS1", PreviousDNS1, INIPath);
INIWrite("暂存数据", "PreviousDNS2", PreviousDNS2, INIPath);
INIWrite("暂存数据", "IsPreviousDnsAutomatic", isIPv4DNSAuto.ToString(), INIPath);
}
}
catch (Exception ex)
{
WriteLog($"无法设置指定的网络适配器!", LogLevel.Error, ex);
if (MessageBox.Show($"无法设置指定的网络适配器!请手动设置!\r\n点击“是”将为您展示有关帮助。", "错误", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes) StartProcess(当您找不到当前正在使用的适配器或启动时遇到适配器设置失败时,useShellExecute: true );
}
WriteLog("完成 SetLoopbackDNS 。", LogLevel.Debug);
}
/// <summary>
/// 从配置文件还原适配器
/// </summary>
/// <param name="Adapter">指定的网络适配器</param>
public void RestoreAdapterDNS(NetworkAdapter Adapter)
{
WriteLog("进入 RestoreAdapterDNS 。", LogLevel.Debug);
try
{
if (Adapter.IPv4DNSServer.Length > 0 && Adapter.IPv4DNSServer[0] == "127.0.0.1")
{
// 指定适配器的首选DNS为环回地址的情况,需要从配置文件还原回去
if (StringToBool(INIRead("暂存数据", "IsPreviousDnsAutomatic", INIPath)))
{
// 指定适配器DNS服务器之前是自动获取的情况,设置指定适配器DNS服务器为自动获取
SetIPv4DNS(Adapter, []);
WriteLog($"活动网络适配器的 IPv4 DNS 成功设置为自动获取。", LogLevel.Info);
}
else
{
string PreviousDNS1 = INIRead("暂存数据", "PreviousDNS1", INIPath);
string PreviousDNS2 = INIRead("暂存数据", "PreviousDNS2", INIPath);
if (string.IsNullOrEmpty(PreviousDNS1) || !IsValidIPv4(PreviousDNS1))
{
SetIPv4DNS(Adapter, []);
WriteLog($"指定网络适配器的 IPv4 DNS 成功设置为自动获取。", LogLevel.Info);
}
else
{
if (string.IsNullOrEmpty(PreviousDNS2) || !IsValidIPv4(PreviousDNS2)) SetIPv4DNS(Adapter, [PreviousDNS1]);
else SetIPv4DNS(Adapter, [PreviousDNS1, PreviousDNS2]);
Adapter = Refresh(Adapter);
string IPv4DNS1 = Adapter.IPv4DNSServer.Length > 0 ? Adapter.IPv4DNSServer[0] : "空";
string IPv4DNS2 = Adapter.IPv4DNSServer.Length > 1 ? Adapter.IPv4DNSServer[1] : "空";
WriteLog($"指定网络适配器的 DNS 成功设置为首选 {IPv4DNS1} ,备用 {IPv4DNS2} 。", LogLevel.Info);
}
}
}
if (Adapter.IPv6DNSServer.Length > 0 && Adapter.IPv6DNSServer[0] == "::1")
{
SetIPv6DNS(Adapter, []);
WriteLog($"活动网络适配器的 IPv6 DNS 成功设置为自动获取。", LogLevel.Info);
}
}
catch(Exception ex)
{
WriteLog($"无法还原指定的网络适配器!", LogLevel.Error, ex);
if (MessageBox.Show($"无法还原指定的网络适配器!请手动还原!\r\n点击“是”将为您展示有关帮助。", "错误", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes) StartProcess(当您在停止时遇到适配器设置失败或不确定该软件是否对适配器造成影响时, useShellExecute: true);
}
WriteLog("完成 RestoreAdapterDNS 。", LogLevel.Debug);
}
/// <summary>
/// 启动主服务
/// </summary>
public void StartNginx()
{
WriteLog("进入 StartNginx 。", LogLevel.Debug);
try
{
ServiceStatusText.Text = "当前服务状态:\r\n主服务启动中";
ServiceStatusText.Foreground = new SolidColorBrush(Colors.DarkOrange);
if (IsPortInUse(80))
{
WriteLog($"检测到系统 80 端口被占用。", LogLevel.Warning);
if (MessageBox.Show($"检测到系统 80 端口被占用,主服务可能无法正常运行。\r\n点击“是”将为您展示有关帮助。", "警告", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes) StartProcess(当您的主服务运行后自动停止或遇到80端口被占用的提示时,useShellExecute: true);
}
StartProcess(nginxPath,workingDirectory: NginxDirectory);
}
catch (Exception ex)
{
WriteLog($"尝试启动主服务时遇到异常。", LogLevel.Error, ex);
MessageBox.Show($"启动主服务时遇到异常:{ex}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
WriteLog("完成 StartNginx 。", LogLevel.Debug);
}
/// <summary>
/// 按需启动服务
/// </summary>
public async Task StartService()
{
WriteLog("进入 StartService 。", LogLevel.Debug);
if (INIRead("高级设置", "DomainNameResolutionMethod", INIPath) == "DnsService")
{
// 域名解析模式是 DNS 服务的情况,需要启动主服务、 DNS 服务与设置适配器
if (!IsProcessRunning(NginxProcessName))
{
WriteLog($"主服务未运行,将启动主服务。", LogLevel.Info);
StartNginx();
}
if (!IsAcrylicServiceRunning())
{
WriteLog($"DNS 服务未运行,将启动 DNS 服务。", LogLevel.Info);
ServiceStatusText.Text = "当前服务状态:\r\nDNS服务启动中";
ServiceStatusText.Foreground = new SolidColorBrush(Colors.DarkOrange);
try
{
await StartAcrylicService();
}
catch (Exception ex)
{
WriteLog($"尝试启动 DNS 服务时遇到异常。", LogLevel.Error, ex);
MessageBox.Show($"启动 DNS 服务时遇到异常:{ex}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// 遍历所有适配器
NetworkAdapter activeAdapter = null;
foreach (var adapter in GetNetworkAdapters(ScopeNeeded.FriendlyNameNotNullOnly))
{
if (adapter.FriendlyName == AdaptersCombo.SelectedItem?.ToString())
{
// 如果适配器的名称和下拉框选中的适配器相同,就记录下来备用并退出循环
activeAdapter = adapter;
break;
}
}
if (activeAdapter != null)
{
WriteLog($"指定网络适配器为: {activeAdapter.FriendlyName}", LogLevel.Info);
SetLoopbackDNS(activeAdapter);
FlushDNSCache();
}
else
{
WriteLog($"没有找到指定的网络适配器!", LogLevel.Warning);
if (MessageBox.Show($"没有找到指定的网络适配器!您可能需要手动设置。\r\n点击“是”将为您展示有关帮助。", "警告", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes) StartProcess(当您找不到当前正在使用的适配器或启动时遇到适配器设置失败时,useShellExecute: true);
}
}
else
{
// 域名解析模式不是 DNS 服务的情况,仅需要启动主服务
if (!IsProcessRunning(NginxProcessName))
{
WriteLog($"主服务未运行,将启动主服务。", LogLevel.Info);
StartNginx();
}
}
// 更新服务的状态信息
UpdateServiceStatus();
WriteLog("完成 StartService 。", LogLevel.Debug);
}
/// <summary>
/// 停止所有服务
/// </summary>
public async Task StopService()
{
WriteLog("进入 StopService 。", LogLevel.Debug);
if (IsProcessRunning("SNIBypass"))
{
WriteLog($"主服务运行中,将停止主服务。", LogLevel.Info);
ServiceStatusText.Text = "当前服务状态:\r\n主服务停止中";
ServiceStatusText.Foreground = new SolidColorBrush(Colors.DarkOrange);
try
{
KillProcess("SNIBypass");
}
catch (Exception ex)
{
WriteLog($"尝试停止主服务时遇到异常。", LogLevel.Error, ex);
MessageBox.Show($"停止主服务时遇到异常:{ex}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
if (IsAcrylicServiceRunning())
{
WriteLog($"DNS 服务运行中,将停止 DNS 服务。", LogLevel.Info);
ServiceStatusText.Text = "当前服务状态:\r\nDNS服务停止中";
ServiceStatusText.Foreground = new SolidColorBrush(Colors.DarkOrange);
try
{
await StopAcrylicService();
}
catch (Exception ex)
{
WriteLog($"尝试停止 DNS 服务时遇到异常。", LogLevel.Error);
MessageBox.Show($"停止 DNS 服务时遇到异常:{ex}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// 更新服务的状态信息
UpdateServiceStatus();
// 获取所有网络适配器
List<NetworkAdapter> adapters = GetNetworkAdapters(ScopeNeeded.FriendlyNameNotNullOnly);
NetworkAdapter activeAdapter = null;
// 遍历所有适配器
foreach (var adapter in adapters)
{
if (adapter.FriendlyName == AdaptersCombo.SelectedItem?.ToString())
{
activeAdapter = adapter;
break;
}
}
if (activeAdapter != null) RestoreAdapterDNS(activeAdapter);
WriteLog("完成 StopService 。", LogLevel.Debug);
}
/// <summary>
/// 刷新状态按钮点击事件
/// </summary>
private void RefreshBtn_Click(object sender, RoutedEventArgs e)
{
WriteLog("进入 RefreshBtn_Click 。", LogLevel.Debug);
UpdateServiceStatus();
WriteLog("完成 RefreshBtn_Click 。", LogLevel.Debug);
}
/// <summary>
/// 启动按钮点击事件
/// </summary>
private async void StartBtn_Click(object sender, RoutedEventArgs e)
{
WriteLog("进入 StartBtn_Click 。", LogLevel.Debug);
// 禁用按钮,防手贱重复启动,此时指定适配器也不可以更改
StartBtn.IsEnabled = StopBtn.IsEnabled = AdaptersCombo.IsEnabled = GetActiveAdapterBtn.IsEnabled = false;
if (INIRead("高级设置", "DomainNameResolutionMethod", INIPath) == "DnsService" && string.IsNullOrEmpty(AdaptersCombo.SelectedItem?.ToString()))
{
// 域名解析为DNS模式但没有选择网络适配器的情况
MessageBox.Show("请先在下拉框中选择当前正在使用的适配器!您可以尝试点击“自动获取”按钮。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
GetActiveAdapterBtn.IsEnabled = AdaptersCombo.IsEnabled = true;
}
else
{
// 从配置文件更新 Hosts
UpdateHostsFromConfig();
// 启动服务
await StartService();
// 实验性功能:Pixiv IP优选
if (StringToBool(INIRead("程序设置", "PixivIPPreference", INIPath))) PixivIPPreference();
FlushDNSCache();
}
// 重新启用按钮
StartBtn.IsEnabled = StopBtn.IsEnabled = true;
WriteLog("完成 StartBtn_Click 。", LogLevel.Debug);
}
/// <summary>
/// 停止按钮点击事件
/// </summary>
private async void StopBtn_Click(object sender, RoutedEventArgs e)
{
WriteLog("进入 StopBtn_Click 。", LogLevel.Debug);
// 禁用按钮,防手重复停止
StartBtn.IsEnabled = false;
StopBtn.IsEnabled = false;
// 移除所有条目以消除对系统的影响
RemoveHostsRecords();
// 停止服务
await StopService();
// 实验性功能:Pixiv IP优选
if (StringToBool(INIRead("程序设置", "PixivIPPreference", INIPath)))
{
try
{
RemoveSection(SystemHosts, "s.pximg.net");
}
catch (UnauthorizedAccessException ex)
{
WriteLog($"对系统 Hosts 的访问被拒绝!", LogLevel.Error, ex);
if (MessageBox.Show($"对系统 Hosts 的访问被拒绝。\r\n{ex}\r\n点击“是”将为您展示有关帮助。", "错误", MessageBoxButton.YesNo, MessageBoxImage.Error) == MessageBoxResult.Yes) StartProcess(当您遇到对系统hosts的访问被拒绝的提示时,useShellExecute: true);
}
catch (Exception ex)
{
WriteLog($"遇到异常。", LogLevel.Error, ex);
MessageBox.Show($"更新 Hosts 文件时遇到异常。\r\n{ex}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// 刷新DNS缓存
FlushDNSCache();
// 重新启用按钮,此时适配器也可以更改
StartBtn.IsEnabled = StopBtn.IsEnabled = AdaptersCombo.IsEnabled = GetActiveAdapterBtn.IsEnabled = true;
WriteLog("完成 StopBtn_Click 。", LogLevel.Debug);
}
/// <summary>
/// 设置开机启动按钮点击事件
/// </summary>
private void SetStartBtn_Click(object sender, RoutedEventArgs e)
{
WriteLog("进入 SetStartBtn_Click 。", LogLevel.Debug);
try
{
// 使用 TaskService 来访问和操作任务计划程序
using (TaskService ts = new())
{
// 尝试获取已存在的同名任务
Microsoft.Win32.TaskScheduler.Task existingTask = ts.GetTask(TaskName);
// 如果任务已存在,则删除它,以便创建新的任务
if (existingTask != null)
{
WriteLog($"计划任务 {TaskName} 已经存在,进行移除。", LogLevel.Warning);
ts.RootFolder.DeleteTask(TaskName);
}
// 创建一个新的任务定义
TaskDefinition td = ts.NewTask();
// 设置任务的描述信息和作者
td.RegistrationInfo.Description = "开机启动 SNIBypassGUI 并自动启动服务";
td.RegistrationInfo.Author = "SNIBypassGUI";
// 将登录触发器添加到任务定义中
td.Triggers.Add(new LogonTrigger());
// 创建一个执行操作,指定要执行的 Nginx 路径、参数和工作目录
ExecAction execAction = new(SNIBypassGUIExeFilePath, null, null);
// 将执行操作添加到任务定义中
td.Actions.Add(execAction);
// 设置为管理员组
td.Principal.GroupId = @"BUILTIN\Administrators";
// 设置任务以最高权限运行
td.Principal.RunLevel = TaskRunLevel.Highest;
// 设置任务所需的安全登录方法为“组”
td.Principal.LogonType = TaskLogonType.Group;
// 在根文件夹中注册新的任务定义
ts.RootFolder.RegisterTaskDefinition(TaskName, td);
}
WriteLog("成功设置 SNIBypassGUI 为开机启动。", LogLevel.Info);
// 显示提示信息,表示已成功设置为开机启动
MessageBox.Show("成功设置 SNIBypassGUI 为开机启动!\r\n当开机自动启动时,将会自动在托盘图标运行并启动服务。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
WriteLog($"尝试设置 SNIBypassGUI 为开机启动时遇到异常。", LogLevel.Error,ex);
MessageBox.Show($"设置开机启动时遇到异常:{ex}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
WriteLog("完成 SetStartBtn_Click 。", LogLevel.Debug);
}
/// <summary>
/// 停止开机启动按钮点击事件
/// </summary>
private void StopStartBtn_Click(object sender, RoutedEventArgs e)
{
WriteLog("进入 StopStartBtn_Click 。", LogLevel.Debug);
try
{
// 使用 TaskService 来访问和操作任务计划程序
using (TaskService ts = new())
{
// 尝试获取已存在的同名任务
Microsoft.Win32.TaskScheduler.Task existingTask = ts.GetTask(TaskName);
// 如果任务已存在,则删除它
if (existingTask != null)
{