龙腾猫跃 4 лет назад
Родитель
Сommit
0066d1b35e
40 измененных файлов с 731 добавлено и 431 удалено
  1. 1 11
      Plain Craft Launcher 2/Application.xaml.vb
  2. 2 2
      Plain Craft Launcher 2/Controls/MyCard.vb
  3. 8 0
      Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb
  4. 67 36
      Plain Craft Launcher 2/Controls/MyPageRight.vb
  5. 73 190
      Plain Craft Launcher 2/FormMain.xaml.vb
  6. 2 2
      Plain Craft Launcher 2/Modules/Base/ModAnimation.vb
  7. 77 27
      Plain Craft Launcher 2/Modules/Base/ModBase.vb
  8. 1 2
      Plain Craft Launcher 2/Modules/Base/ModLoader.vb
  9. 21 2
      Plain Craft Launcher 2/Modules/Base/ModNet.vb
  10. 2 2
      Plain Craft Launcher 2/Modules/Base/ModValidate.vb
  11. 19 7
      Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
  12. 16 5
      Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
  13. 22 17
      Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
  14. 12 13
      Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
  15. 2 2
      Plain Craft Launcher 2/My Project/AssemblyInfo.vb
  16. 0 10
      Plain Craft Launcher 2/My Project/Resources.Designer.vb
  17. 0 3
      Plain Craft Launcher 2/My Project/Resources.resx
  18. 10 14
      Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
  19. 2 2
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCfDetail.xaml.vb
  20. 3 3
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb
  21. 1 1
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMojangSkin.xaml.vb
  22. 2 1
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb
  23. 29 25
      Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml
  24. 0 1
      Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml.vb
  25. 108 0
      Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml
  26. 4 2
      Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml
  27. 8 18
      Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml
  28. 192 0
      Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml.vb
  29. 1 1
      Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb
  30. 7 7
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb
  31. 8 4
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml
  32. 13 5
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb
  33. 0 1
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb
  34. 2 2
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb
  35. 4 0
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb
  36. 1 1
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb
  37. 11 12
      Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
  38. BIN
      Plain Craft Launcher 2/Resources/Help.zip
  39. BIN
      Plain Craft Launcher 2/Resources/STUN.dll
  40. BIN
      Plain Craft Launcher 2/Resources/java-wrapper.jar

+ 1 - 11
Plain Craft Launcher 2/Application.xaml.vb

@@ -30,7 +30,7 @@ Public Class Application
                     UpdateReplace(e.Args(1), e.Args(2).Trim(""""), e.Args(3).Trim(""""), e.Args(4))
                     Environment.[Exit](Result.Cancel)
                     Exit Sub
-                ElseIf e.Args(0) = "--link" Then
+                ElseIf e.Args(0).StartsWith("--link") Then
                     '稍作等待后切换到联机页面
                     Thread.Sleep(1000)
                     FormMain.IsLinkRestart = True
@@ -142,11 +142,9 @@ Public Class Application
     End Sub
 
     '动态 DLL 调用
-    Private Shared AssemblyStun As Assembly
     Private Shared AssemblyNAudio As Assembly
     Private Shared AssemblyJson As Assembly
     Private Shared AssemblyDialog As Assembly
-    Private Shared ReadOnly AssemblyStunLock As New Object
     Private Shared ReadOnly AssemblyNAudioLock As New Object
     Private Shared ReadOnly AssemblyJsonLock As New Object
     Private Shared ReadOnly AssemblyDialogLock As New Object
@@ -175,14 +173,6 @@ Public Class Application
                 End If
                 Return AssemblyDialog
             End SyncLock
-        ElseIf args.Name.StartsWith("STUN") Then
-            SyncLock AssemblyStunLock
-                If AssemblyStun Is Nothing Then
-                    Log("[Start] 加载 DLL:STUN")
-                    AssemblyStun = Assembly.Load(GetResources("STUN"))
-                End If
-                Return AssemblyStun
-            End SyncLock
         Else
             Return Nothing
         End If

+ 2 - 2
Plain Craft Launcher 2/Controls/MyCard.vb

@@ -25,9 +25,9 @@
     Public Sub New()
         MainChrome = New SystemDropShadowChrome With {.Margin = New Thickness(-9.5, -9, 0.5, -0.5), .Opacity = 0.1, .CornerRadius = New CornerRadius(6)}
         MainChrome.SetResourceReference(SystemDropShadowChrome.ColorProperty, "ColorObject1")
-        Children.Add(MainChrome)
+        Children.Insert(0, MainChrome)
         MainBorder = New Border With {.Background = New SolidColorBrush(Color.FromArgb(205, 255, 255, 255)), .CornerRadius = New CornerRadius(6), .IsHitTestVisible = False}
-        Children.Add(MainBorder)
+        Children.Insert(1, MainBorder)
         MainGrid = New Grid
         Children.Add(MainGrid)
     End Sub

+ 8 - 0
Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb

@@ -30,6 +30,7 @@
         Color
         White
         Black
+        Red
         Custom
     End Enum
     Public Property Theme As Themes = Themes.Color
@@ -101,6 +102,8 @@
                             AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrush2", AnimationColorIn))
                         Case Themes.White
                             AnimList.Add(AaColor(PanBack, BackgroundProperty, New MyColor(50, 255, 255, 255) - PanBack.Background, AnimationColorIn))
+                        Case Themes.Red
+                            AnimList.Add(AaColor(Path, Shape.FillProperty, New MyColor(255, 76, 76) - Path.Fill, AnimationColorIn))
                         Case Themes.Black
                             AnimList.Add(AaColor(Path, Shape.FillProperty, New MyColor(230, 0, 0, 0) - Path.Fill, AnimationColorIn))
                         Case Themes.Custom
@@ -117,6 +120,9 @@
                         Case Themes.White
                             AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrush8", AnimationColorOut))
                             AnimList.Add(AaColor(PanBack, BackgroundProperty, New MyColor(0, 255, 255, 255) - PanBack.Background, AnimationColorOut))
+                        Case Themes.Red
+                            AnimList.Add(AaColor(Path, Shape.FillProperty, New MyColor(160, 255, 76, 76) - Path.Fill, AnimationColorOut))
+                            PanBack.Background = New MyColor(0, 255, 255, 255)
                         Case Themes.Black
                             AnimList.Add(AaColor(Path, Shape.FillProperty, New MyColor(160, 0, 0, 0) - Path.Fill, AnimationColorOut))
                             PanBack.Background = New MyColor(0, 255, 255, 255)
@@ -135,6 +141,8 @@
                         Path.SetResourceReference(Shape.FillProperty, "ColorBrush5")
                     Case Themes.White
                         Path.SetResourceReference(Shape.FillProperty, "ColorBrush8")
+                    Case Themes.Red
+                        Path.Fill = New MyColor(160, 255, 76, 76)
                     Case Themes.Black
                         Path.Fill = New MyColor(160, 0, 0, 0)
                     Case Themes.Custom

+ 67 - 36
Plain Craft Launcher 2/Controls/MyPageRight.vb

@@ -12,7 +12,7 @@
         LoaderExit '加载环退出动画
         ContentEnter '内容进入动画
         ContentStay '内容正常显示
-        ReloadExit '刷新导致的全部退出动画
+        ContentExit '刷新导致的全部退出动画,或页面内容退出(子页面更改)导致的全部退出动画
         PageExit '切换页面导致的全部退出动画
     End Enum
     Private _PageState As PageStates = PageStates.Empty
@@ -48,7 +48,8 @@
     ''' <param name="RealLoader">在工作线程执行的加载器。</param>
     ''' <param name="FinishedInvoke">当加载器执行完成,在 UI 线程触发的 UI 初始化事件。</param>
     Public Sub PageLoaderInit(LoaderUi As MyLoading, PanLoader As FrameworkElement, PanContent As FrameworkElement, PanAlways As FrameworkElement,
-                              RealLoader As LoaderBase, FinishedInvoke As Action(Of LoaderBase), Optional InputInvoke As Func(Of Object) = Nothing,
+                              RealLoader As LoaderBase,
+                              Optional FinishedInvoke As Action(Of LoaderBase) = Nothing, Optional InputInvoke As Func(Of Object) = Nothing,
                               Optional AutoRun As Boolean = True)
         '初始化参数
         Me.PanLoader = PanLoader
@@ -59,14 +60,16 @@
         Me.PageLoaderInputInvoke = InputInvoke
         Me.PageLoaderAutoRun = AutoRun
         '添加结束 Invoke
-        AddHandler RealLoader.PreviewFinish,
-            Sub()
-                Do While PageState = MyPageRight.PageStates.PageExit OrElse PageState = MyPageRight.PageStates.ReloadExit
-                    Thread.Sleep(10) '不在退出动画时执行 UI 线程操作,避免退出动画被重置
+        If FinishedInvoke IsNot Nothing Then
+            AddHandler RealLoader.PreviewFinish,
+                Sub()
+                    Do While PageState = MyPageRight.PageStates.PageExit OrElse PageState = MyPageRight.PageStates.ContentExit
+                        Thread.Sleep(10) '不在退出动画时执行 UI 线程操作,避免退出动画被重置
                 Loop
-                RunInUiWait(Sub() FinishedInvoke(RealLoader))
-                Thread.Sleep(20) '由于大量初始化控件会导致掉帧,延迟触发 State 改变事件
+                    RunInUiWait(Sub() FinishedInvoke(RealLoader))
+                    Thread.Sleep(20) '由于大量初始化控件会导致掉帧,延迟触发 State 改变事件
             End Sub
+        End If
         AddHandler RealLoader.OnStateChangedUi, Sub(Loader As LoaderBase, NewState As LoadState, OldState As LoadState) RunInUi(Sub() PageLoaderState(Loader, NewState, OldState))
         '隐藏 UI
         PanLoader.Visibility = Visibility.Collapsed
@@ -82,14 +85,13 @@
                 PageLoader.Start(Input)
             End If
         End If
-        If PageLoader.State = LoadState.Finished Then
+        If PageLoader.State = LoadState.Finished AndAlso FinishedInvoke IsNot Nothing Then
             RunInUiWait(Sub() FinishedInvoke(RealLoader)) '加载器已提前完成,直接触发事件
         End If
         '设置加载环
         PageLoaderUi.State = RealLoader
         AddHandler PageLoaderUi.Click, Sub() If RealLoader.State = LoadState.Failed Then PageLoaderRestart() '点击重试事件
     End Sub
-
     '重试
     Public Sub PageLoaderRestart(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = True) '由外部调用的重试
         If Not PageLoaderAutoRun Then Exit Sub
@@ -112,32 +114,38 @@
     ''' </summary>
     Public Sub PageOnEnter()
         If ModeDebug Then Log("[UI] 已触发 PageOnEnter")
-        RaiseEvent OnPageEnter()
-
+        RaiseEvent PageEnter()
         Select Case PageState
             Case PageStates.Empty
-                If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Aborted Then
+                If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Aborted Then
+                    '如果加载器在进入页面时不启动(例如 HiPer 联机),那么在此时就会有 State = Waiting
                     PageState = PageStates.ContentEnter
                     TriggerEnterAnimation(PanAlways, If(PanContent, Child))
                 ElseIf PageLoader.State = LoadState.Loading Then
                     PageState = PageStates.LoaderWait
                     AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 200), "PageRight PageChange " & PageUuid)
-                ElseIf PageLoader.State = LoadState.Failed Then
+                Else 'PageLoader.State = LoadState.Failed
                     PageState = PageStates.LoaderEnter
                     TriggerEnterAnimation(PanAlways, PanLoader)
-                ElseIf PageLoader.State = LoadState.Waiting Then
-                    '由于 “页面加载器 Minecraft Version List 不应在 PageOnEnter 事件出现 Waiting 状态” 的神秘反馈添加的临时修复
+                End If
+            Case PageStates.ContentExit
+                '和上面的一样,但是不管 PanAlways
+                If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Aborted Then
+                    PageState = PageStates.ContentEnter
+                    TriggerEnterAnimation(If(PanContent, Child))
+                ElseIf PageLoader.State = LoadState.Loading Then
                     PageState = PageStates.LoaderWait
                     AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 200), "PageRight PageChange " & PageUuid)
-                    Log("[Anim] 警告:页面加载器 " & PageLoader.Name & " 不应在 PageOnEnter 事件出现 " & GetStringFromEnum(PageLoader.State) & " 状态", LogLevel.Debug)
-                Else
-                    Throw New Exception("页面加载器 " & PageLoader.Name & " 不应在 PageOnEnter 事件出现 " & GetStringFromEnum(PageLoader.State) & " 状态。")
+                Else 'PageLoader.State = LoadState.Failed
+                    PageState = PageStates.LoaderEnter
+                    TriggerEnterAnimation(PanLoader)
                 End If
+            Case PageStates.ContentEnter '重复调用 PageOnEnter,直接忽略
             Case Else
                 Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnEnter 事件。")
         End Select
     End Sub
-    Public Event OnPageEnter()
+    Public Event PageEnter()
     ''' <summary>
     ''' 需要切换到其他页面。
     ''' 需要立即切换至 PageExit 或 Empty。
@@ -154,7 +162,7 @@
             Case PageStates.LoaderWait
                 PageState = PageStates.PageExit
                 TriggerExitAnimation(PanAlways)
-            Case PageStates.LoaderExit, PageStates.ReloadExit
+            Case PageStates.LoaderExit, PageStates.ContentExit
                 PageState = PageStates.PageExit
                 If PanAlways IsNot Nothing Then TriggerExitAnimation(PanAlways, If(PanContent, Child))
             Case PageStates.PageExit, PageStates.Empty
@@ -178,6 +186,29 @@
             If PanAlways IsNot Nothing Then PanAlways.Visibility = Visibility.Collapsed
         End If
     End Sub
+    ''' <summary>
+    ''' PanContent 中的子页面改变,需要让当前内容退出,再显示新的内容。
+    ''' 需要在 PageEnter 事件确认要显示的子页面有哪些。
+    ''' </summary>
+    Public Sub PageOnContentExit()
+        If ModeDebug Then Log("[UI] 已触发 PageOnContentExit")
+        If PageLoader IsNot Nothing AndAlso PageLoader.State = LoadState.Loading Then
+            Throw New Exception("在调用 PageOnContentExit 时,加载器不能为 Loading 状态")
+            'Loading 的加载器可能触发进一步变化,难以预测会触发子页面的动画还是加载器完成的动画
+        End If
+        Select Case PageState
+            Case PageStates.ContentEnter, PageStates.ContentStay
+                PageState = PageStates.ContentExit
+                TriggerExitAnimation(PanContent)
+            Case PageStates.LoaderExit
+                PageState = PageStates.ContentExit
+            Case PageStates.LoaderEnter, PageStates.LoaderStayForce, PageStates.LoaderStay
+                PageState = PageStates.ContentExit
+                TriggerExitAnimation(PanLoader)
+            Case PageStates.LoaderWait, PageStates.Empty
+                PageOnEnter()
+        End Select
+    End Sub
 
     '内部触发的事件
     ''' <summary>
@@ -205,8 +236,7 @@
         Select Case PageState
             Case PageStates.PageExit
                 PageState = PageStates.Empty
-            Case PageStates.ReloadExit
-                PageState = PageStates.Empty
+            Case PageStates.ContentExit
                 PageOnEnter()
             Case PageStates.LoaderExit
                 PageState = PageStates.ContentEnter
@@ -258,20 +288,20 @@
     Private Sub PageLoaderState(sender As Object, NewState As LoadState, OldState As LoadState)
         Select Case NewState
             Case LoadState.Failed, LoadState.Loading
-                If Not OldState = LoadState.Finished Then Exit Sub
-                If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Refresh)")
-                '重新开始运行
+                If OldState = LoadState.Failed OrElse OldState = LoadState.Loading Then Exit Sub
+                If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Start/Refresh)")
+                '(重新)开始运行
                 '需要从部分状态切换到 ReloadExit
                 Select Case PageState
                     Case PageStates.ContentEnter, PageStates.ContentStay
-                        PageState = PageStates.ReloadExit
+                        PageState = PageStates.ContentExit
                         TriggerExitAnimation(PanContent)
-                    Case PageStates.LoaderExit, PageStates.ReloadExit
-                        PageState = PageStates.ReloadExit
+                    Case PageStates.LoaderExit
+                        PageState = PageStates.ContentExit
                 End Select
-            Case LoadState.Finished
-                If Not OldState = LoadState.Loading Then Exit Sub
-                If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Stop)")
+            Case LoadState.Finished, LoadState.Aborted, LoadState.Waiting
+                If Not (OldState = LoadState.Failed OrElse OldState = LoadState.Loading) Then Exit Sub
+                If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Stop/Abort)")
                 '运行结束
                 '需要从 LoaderWait 切换到 ContentEnter,或从 LoaderStay 切换到 LoaderExit
                 Select Case PageState
@@ -360,13 +390,14 @@
     End Function
     Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
         If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Exit Sub
-        If TypeOf Element Is MyCard OrElse TypeOf Element Is MyHint Then
+        If TypeOf Element Is MyCard OrElse TypeOf Element Is MyHint OrElse TypeOf Element Is TextBlock OrElse TypeOf Element Is MyTextButton Then
             AllControls.Add(Element)
         ElseIf TypeOf Element Is ContentControl Then
-            GetAllAnimControls(CType(Element, ContentControl).Content, AllControls, IgnoreInvisibility)
+            Dim Content = CType(Element, ContentControl).Content
+            If Content IsNot Nothing AndAlso TypeOf Content Is FrameworkElement Then GetAllAnimControls(Content, AllControls, IgnoreInvisibility)
         ElseIf TypeOf Element Is Panel Then
-            For Each Element2 As FrameworkElement In CType(Element, Panel).Children
-                GetAllAnimControls(Element2, AllControls, IgnoreInvisibility)
+            For Each Element2 In CType(Element, Panel).Children
+                If TypeOf Element2 Is FrameworkElement Then GetAllAnimControls(Element2, AllControls, IgnoreInvisibility)
             Next
         End If
     End Sub

+ 73 - 190
Plain Craft Launcher 2/FormMain.xaml.vb

@@ -17,7 +17,7 @@ Public Class FormMain
             BugCount += 9
         End If
         If LastVersion < 260 Then 'Release 2.3.2
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "解决了联机人数 ≥3 人时出现的频繁掉线或突发高延迟的问题"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "解决了联机人数 ≥3 人时出现的频繁掉线或突发高延迟的问题"))
             FeatureCount += 22
             BugCount += 4
         End If
@@ -32,22 +32,21 @@ Public Class FormMain
             BugCount += 3
         End If
         If LastVersion < 253 Then 'Release 2.2.13
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(7, "支持在安装菜单中直接安装 OptiFabric"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "支持调整 Mod 文件名中中文译名的位置"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "崩溃分析优化,支持分析更多 Forge 相关的崩溃"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "支持在安装菜单中直接安装 OptiFabric"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持调整 Mod 文件名中中文译名的位置"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "崩溃分析优化,支持分析更多 Forge 相关的崩溃"))
             FeatureCount += 13
             BugCount += 27
         End If
         If LastVersion < 250 Then 'Release 2.2.11
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "支持将 Mod 文件拖入窗口进行安装"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "支持将 Mod 文件拖入窗口进行安装"))
             If LastVersion = 246 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法使用中文搜索 Mod 的 Bug"))
             FeatureCount += 8
             BugCount += 33
         End If
         If LastVersion < 246 Then 'Release 2.2.9
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "Mod 下载支持筛选 Forge Mod 与 Fabric Mod"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "大幅修改联机页面布局(作为联机优化的第二个阶段)"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "Mod、整合包下载的综合优化"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "Mod 下载支持筛选 Forge Mod 与 Fabric Mod"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "Mod、整合包下载的综合优化"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复使用离线登录进入 Forge 的存档可能导致游戏崩溃"))
             FeatureCount += 13
             BugCount += 11
@@ -62,92 +61,41 @@ Public Class FormMain
             BugCount += 2
         End If
         If LastVersion < 236 Then 'Release 2.2.5
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "网络状态检测支持检测 Windows 防火墙与网络延迟"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "重制右侧页面的切换动画,卡片与提示条将逐个进入退出,并具有独特的位移动画"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "添加 MC 1.18 中要求 Java 17 的检测与提醒"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "优化页面切换、下载的性能"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "允许拖拽按钮的设置第三方登录"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复网络状态检测的结果总为 A 级的 Bug"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法下载新版本的 Forge 的 Bug"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "网络状态检测支持检测 Windows 防火墙与网络延迟"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "重制右侧页面的切换动画,卡片与提示条将逐个进入退出,并具有独特的位移动画"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "添加 MC 1.18 中要求 Java 17 的检测与提醒"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化页面切换、下载的性能"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "允许拖拽按钮设置第三方登录"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "修复无法下载新版本的 Forge 的 Bug"))
             FeatureCount += 25
             BugCount += 22
         End If
         If LastVersion < 233 Then 'Release 2.2.3
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(7, "微软账号支持更换皮肤与披风"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "支持拖拽整合包文件到 PCL2 进行快捷安装"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "Mod 下载支持显示对应的 MC 版本与 Mod 加载器,且可以跳转到 MCBBS 介绍页"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "支持安装 1.17 OptiFine + Forge"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "使用 Mojang 账号登录将会显示迁移提醒"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "微软账号支持更换皮肤与披风"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "支持拖拽整合包文件到 PCL2 进行快捷安装"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "Mod 下载支持显示对应的 MC 版本与 Mod 加载器,且可以跳转到 MCBBS 介绍页"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持安装 1.17 OptiFine + Forge"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "使用 Mojang 账号登录将会显示迁移提醒"))
             FeatureCount += 12
             BugCount += 26
         End If
         If LastVersion < 231 Then 'Release 2.2.1, Also 229
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(9, "联机功能的早期测试(尚未完成,延迟很高,之后会优化的)"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "添加 1.18 实验性快照 5 的特供下载,且支持列表的联网更新"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "添加 1.18 实验性快照 5 的特供下载,且支持列表的联网更新"))
             FeatureCount += 11
             BugCount += 3
         End If
-        If LastVersion < 226 Then 'Release 2.1.4
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "支持 1.17.1 Forge、Fabric 的安装与启动"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "优化 Forge 下载与安装速度,提高安装稳定性"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "更新内置帮助库,添加数个指南页面与整合包、存档的安装教程"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持将游戏文件夹压缩包作为游戏整合包导入"))
-            FeatureCount += 12
-            BugCount += 13
-        End If
-        If LastVersion < 224 Then 'Release 2.1.3
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "界面与动画综合优化,重新制作主题配色"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "添加 1.18 实验性快照的特供下载"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "更新内置帮助库,添加光影、数据包、Mod 等资源的安装教程"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "游戏崩溃分析优化"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "若设置为自动更新,PCL2 会在关闭时才替换文件"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "追加大量千万████的可能性"))
-            'FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持 1.17 Forge 的安装与启动"))
-            FeatureCount += 33
-            BugCount += 20
-        End If
-        If LastVersion < 217 Then 'Release 2.0.5
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(7, "可以根据游戏版本自动选择所需的 Java"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "允许在 Java 版本检测不通过时强制指定使用 Java"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "允许为不同版本独立设置 Java"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "Java 选择与搜索的综合优化"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法使用 Mod、整合包搜索功能的 Bug"))
-            FeatureCount += 8
-            BugCount += 20
-        End If
-        If LastVersion < 214 Then 'Release 2.0.2
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "添加一键关闭所有运行中的 Minecraft 的按钮"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "增加对 MC 1.17 需要 Java 16 的检测与说明"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "Minecraft 崩溃分析优化"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法修改部分个性化设置的严重 Bug"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复 Windows 7 可能无法登录微软账号的严重 Bug"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复网络不稳定时安装整合包极易失败的严重 Bug"))
-            FeatureCount += 10
-            BugCount += 13
-        End If
-        If LastVersion < 212 Then 'Release 2.0.0
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(9, "添加帮助页面,且可以自行添加、删除其中的内容"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "支持安装 MCBBS 格式的整合包"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "自定义主页/帮助页面的 XAML 功能扩展"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "同时安装 OptiFine 与 Forge 时,OptiFine 将作为 Mod 安装"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "优化 CurseForge 搜索"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化多个报错提示,更加人性化"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "适配适用于快照版 MC 的 OptiFine"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复进入 OptiFine 下载页面导致卡死的严重 Bug"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法启动部分 OptiFine+Forge 版本的 Bug"))
-            FeatureCount += 20
-            BugCount += 42
-        End If
 #Else
-        '9:大+
-        '8:中大+
-        '7:中+,大*
-        '6:中小型+,中大*
-        '5:小型+,中*
-        '4:中小*
-        '3:小*
-        '2:极度严重的 Bug
-        '1:严重的 Bug
+        '5:          FEAT+
+        '4:     IMP+ FEAT*
+        '3:BUG+ IMP* FEAT-
+        '2:BUG* IMP-
+        '1:BUG-
+        If LastVersion < 266 Then 'Snapshot 2.4.0
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "追加新的联机方式(HiPer 联机模块)"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "游戏崩溃的弹窗添加了直接查看日志的选项"))
+            FeatureCount += 3
+            BugCount += 8
+        End If
         If LastVersion < 264 Then 'Snapshot 2.3.5
             FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法启动部分 Forge 版本的 Bug"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复多个可能导致 OptiFine、Forge 安装失败的 Bug"))
@@ -160,7 +108,7 @@ Public Class FormMain
             BugCount += 5
         End If
         If LastVersion < 262 Then 'Snapshot 2.3.3
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法使用 Mod、整合包搜索功能的 Bug"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "修复无法使用 Mod、整合包搜索功能的 Bug"))
             BugCount += 1
         End If
         If LastVersion < 261 Then 'Snapshot 2.3.2
@@ -168,11 +116,10 @@ Public Class FormMain
             BugCount += 4
         End If
         If LastVersion < 259 Then 'Snapshot 2.3.1
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "解决了联机人数 ≥3 人时出现的频繁掉线或突发高延迟的问题"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "解决了联机人数 ≥3 人时出现的频繁掉线或突发高延迟的问题"))
             FeatureCount += 14
         End If
         If LastVersion < 258 Then 'Snapshot 2.3.0
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复下载部分 Mod、整合包的 Bug"))
             FeatureCount += 4
             BugCount += 4
         End If
@@ -182,19 +129,19 @@ Public Class FormMain
             BugCount += 3
         End If
         If LastVersion < 254 Then 'Snapshot 2.2.13
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "支持调整 Mod 文件名中中文译名的位置"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持调整 Mod 文件名中中文译名的位置"))
             FeatureCount += 3
             BugCount += 17
         End If
         If LastVersion < 252 Then 'Snapshot 2.2.12
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(7, "支持在安装菜单中直接安装 OptiFabric"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "崩溃分析优化,支持分析更多 Forge 相关的崩溃"))
-            If LastVersion = 251 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复同时安装 Forge 和 OptiFine 时 OptiFine 无效的 Bug"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "支持在安装菜单中直接安装 OptiFabric"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "崩溃分析优化,支持分析更多 Forge 相关的崩溃"))
+            If LastVersion = 251 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复同时安装 Forge 和 OptiFine 时 OptiFine 无效的 Bug"))
             FeatureCount += 10
             BugCount += 10
         End If
         If LastVersion < 251 Then 'Snapshot 2.2.11
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "支持将 Mod 文件拖入窗口进行安装"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持将 Mod 文件拖入窗口进行安装"))
             FeatureCount += 2
             BugCount += 16
         End If
@@ -204,14 +151,13 @@ Public Class FormMain
             BugCount += 17
         End If
         If LastVersion < 247 Then 'Snapshot 2.2.9
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "Mod 下载支持筛选 Forge Mod 与 Fabric Mod"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "Mod 下载支持筛选 Forge Mod 与 Fabric Mod"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "Mod、整合包下载的综合优化"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复使用离线登录进入 Forge 的存档可能导致游戏崩溃"))
             FeatureCount += 8
             BugCount += 4
         End If
         If LastVersion < 245 Then 'Snapshot 2.2.8
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "大幅修改联机页面布局(作为联机优化的第二个阶段)"))
             FeatureCount += 5
             BugCount += 7
         End If
@@ -221,116 +167,48 @@ Public Class FormMain
         End If
         If LastVersion < 239 Then 'Snapshot 2.2.6
             If LastVersion >= 235 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复自动更新由于路径错误失败的 Bug"))
-            If LastVersion = 237 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法使用联机模块的 Bug"))
+            If LastVersion = 237 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "修复无法使用联机模块的 Bug"))
             FeatureCount += 1
             BugCount += 1
         End If
         If LastVersion < 237 Then 'Snapshot 2.2.5
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "允许拖拽按钮的设置第三方登录"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "允许拖拽按钮设置第三方登录"))
             If LastVersion = 235 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复在启动游戏时有可能卡死的 Bug"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法下载新版本的 Forge 的 Bug"))
             FeatureCount += 2
             BugCount += 3
         End If
         If LastVersion < 235 Then 'Snapshot 2.2.4
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "网络状态检测支持检测 Windows 防火墙与网络延迟"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "重制右侧页面的切换动画,卡片与提示条将逐个进入退出,并具有独特的位移动画"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "添加 MC 1.18 中要求 Java 17 的检测与提醒"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "优化页面切换、下载的性能"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复网络状态检测的结果总为 A 级的 Bug"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "网络状态检测支持检测 Windows 防火墙与网络延迟"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "重制右侧页面的切换动画,卡片与提示条将逐个进入退出,并具有独特的位移动画"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "添加 MC 1.18 中要求 Java 17 的检测与提醒"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化页面切换、下载的性能"))
             FeatureCount += 23
             BugCount += 19
         End If
         If LastVersion < 234 Then 'Snapshot 2.2.3
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "支持拖拽整合包文件到 PCL2 进行快捷安装"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "支持安装 1.17 OptiFine + Forge"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "Mod 详情页面支持跳转 MCBBS 介绍页"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "支持拖拽整合包文件到 PCL2 进行快捷安装"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "支持安装 1.17 OptiFine + Forge"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "Mod 详情页面支持跳转 MCBBS 介绍页"))
             FeatureCount += 10
             BugCount += 17
         End If
         If LastVersion < 232 Then 'Snapshot 2.2.2
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(7, "微软账号支持更换皮肤与披风"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "支持显示 Mod 与整合包对应的 MC 版本与 Mod 加载器名称"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "使用 Mojang 账号登录将会显示迁移提醒"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "微软账号支持更换皮肤与披风"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持显示 Mod 与整合包对应的 MC 版本与 Mod 加载器名称"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "使用 Mojang 账号登录将会显示迁移提醒"))
             FeatureCount += 2
             BugCount += 9
         End If
         If LastVersion < 230 Then 'Snapshot 2.2.1, Also 228
-            If LastVersion = 227 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "联机功能的小优化"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "添加 1.18 实验性快照 5 的特供下载,且支持列表的联网更新"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复 Win7 上使用联机功能导致日志文件极大,占用硬盘空间的 Bug"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "添加 1.18 实验性快照 5 的特供下载,且支持列表的联网更新"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复 Win7 上 IOI 联机导致日志文件极大,占用硬盘空间的 Bug"))
             FeatureCount += 12
             BugCount += 4
         End If
         If LastVersion < 227 Then 'Snapshot 2.2.0
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(9, "新增联机功能的早期测试版本"))
             FeatureCount += 2
             BugCount += 2
         End If
-        If LastVersion < 225 Then 'Snapshot 2.1.4
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "支持 1.17.1 Forge、Fabric 的安装与启动"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "优化 Forge 下载与安装速度,提高安装稳定性"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "更新内置帮助库,添加数个指南页面与整合包、存档的安装教程"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持将游戏文件夹压缩包作为游戏整合包导入"))
-            FeatureCount += 12
-            BugCount += 13
-        End If
-        If LastVersion < 223 Then 'Snapshot 2.1.3
-            BugCount += 4
-        End If
-        If LastVersion < 222 Then 'Snapshot 2.1.2
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "更新内置帮助库,添加光影、数据包、Mod 的安装教程"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "游戏崩溃分析优化"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复登录微软账号启动游戏时报错的 Bug"))
-            FeatureCount += 8
-            BugCount += 11
-        End If
-        If LastVersion < 221 Then 'Snapshot 2.1.1
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "界面与动画综合优化,重新制作主题配色"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "若识别码不变,更新密钥在输入一次后会一直有效"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "若设置为自动更新,PCL2 会在关闭时才替换文件"))
-            FeatureCount += 10
-            BugCount += 5
-        End If
-        If LastVersion < 220 Then 'Snapshot 2.1.0
-            'FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "界面与动画综合优化"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "追加大量千万████的可能性"))
-            FeatureCount += 15
-        End If
-        If LastVersion < 219 Then 'Snapshot 2.0.5
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法使用 Mod、整合包搜索功能的 Bug"))
-            BugCount += 2
-        End If
-        If LastVersion < 218 Then 'Snapshot 2.0.4
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "允许为不同版本独立设置 Java"))
-            BugCount += 12
-        End If
-        If LastVersion < 216 Then 'Snapshot 2.0.3
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(7, "可以根据游戏版本自动选择所需的 Java"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "允许在 Java 版本检测不通过时强制指定使用 Java"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "Java 选择与搜索的综合优化"))
-            FeatureCount += 8
-            BugCount += 7
-        End If
-        If LastVersion < 215 Then 'Snapshot 2.0.2
-            If LastVersion = 213 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复账号切换、版本选择下拉框无法展开的严重 Bug"))
-            If LastVersion = 213 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复误判部分客户端需要 Java 16,导致无法启动游戏的严重 Bug"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复 Windows 7 可能无法登录微软账号的严重 Bug"))
-            BugCount += 1
-        End If
-        If LastVersion < 213 Then 'Snapshot 2.0.1
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(6, "添加一键关闭所有运行中的 Minecraft 的按钮"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "增加对 MC 1.17 需要 Java 16 的检测与说明"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "Minecraft 崩溃分析优化"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复网络不稳定时安装整合包极易失败的严重 Bug"))
-            FeatureCount += 10
-            BugCount += 12
-        End If
-        If LastVersion < 211 Then 'Snapshot 2.0.0
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "适配适用于快照版 MC 的 OptiFine"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复进入 OptiFine 下载页面导致卡死的严重 Bug"))
-            BugCount += 3
-        End If
 #End If
         '整理更新日志文本
         Dim ContentList As New List(Of String)
@@ -361,7 +239,10 @@ Public Class FormMain
 
     '窗口加载
     Private IsWindowLoadFinished As Boolean = False
-    Public Shared IsLinkRestart As Boolean = False '是否为联机提权后自动重启
+    ''' <summary>
+    ''' 是否为联机提权后自动重启。
+    ''' </summary>
+    Public Shared IsLinkRestart As Boolean = False
     Public Sub New()
         ApplicationStartTick = GetTimeTick()
         '窗体参数初始化
@@ -392,7 +273,7 @@ Public Class FormMain
         PanMainLeft.Child = FrmLaunchLeft
         PanMainRight.Child = FrmLaunchRight
         FrmLaunchRight.PageState = MyPageRight.PageStates.ContentStay
-        If IsLinkRestart Then PageChange(PageType.Link)
+        If IsLinkRestart Then PageChange(PageType.Link, PageSubType.LinkIoi)
         '模式提醒
 #If DEBUG Then
         Hint("[开发者模式] PCL 正以开发者模式运行,这可能会造成严重的性能下降,请务必立即向开发者反馈此问题!", HintType.Critical)
@@ -469,7 +350,7 @@ Public Class FormMain
         RunInNewThread(Sub()
                            If Not Setup.Get("SystemEula") Then
 Reopen:
-                               Select Case MyMsgBox("在使用 PCL2 前,请同意 PCL2 的用户协议与免责声明。", "协议授权", "同意", "拒绝", "打开用户协议与免责声明页面")
+                               Select Case MyMsgBox("在使用 PCL2 前,请同意 PCL2 的用户协议与免责声明。", "协议授权", "同意", "拒绝", "查看用户协议与免责声明")
                                    Case 1
                                        Setup.Set("SystemEula", True)
                                    Case 2
@@ -481,7 +362,6 @@ Reopen:
                            End If
                            Try
                                Thread.Sleep(200)
-                               If Setup.Get("LinkAuto") Then PageLinkIoi.InitLoader.Start()
                                DlClientListMojangLoader.Start(1)
                                RunCountSub()
                                ServerLoader.Start(1)
@@ -558,9 +438,13 @@ Reopen:
                      "多谢各位的理解啦!", "重新解锁提醒")
         End If
         '移动自定义皮肤
-        If LastVersionCode <= 161 AndAlso File.Exists(Path & "PCL\CustomSkin.png") AndAlso Not File.Exists(PathTemp & "CustomSkin.png") Then
-            CopyFile(Path & "PCL\CustomSkin.png", PathTemp & "CustomSkin.png")
-            Log("[Start] 已移动离线自定义皮肤")
+        If LastVersionCode <= 161 AndAlso File.Exists(Path & "PCL\CustomSkin.png") AndAlso Not File.Exists(PathAppdata & "CustomSkin.png") Then
+            CopyFile(Path & "PCL\CustomSkin.png", PathAppdata & "CustomSkin.png")
+            Log("[Start] 已移动离线自定义皮肤 (162)")
+        End If
+        If LastVersionCode <= 263 AndAlso File.Exists(PathTemp & "CustomSkin.png") AndAlso Not File.Exists(PathAppdata & "CustomSkin.png") Then
+            CopyFile(PathTemp & "CustomSkin.png", PathAppdata & "CustomSkin.png")
+            Log("[Start] 已移动离线自定义皮肤 (264)")
         End If
         '解除帮助页面的隐藏
         If LastVersionCode <= 205 Then
@@ -632,13 +516,14 @@ Reopen:
         IsProgramEnded = True
         AniControlEnabled += 1
         PageLinkIoi.IoiStop(False)
+        PageLinkHiper.HiperStop(False)
         If IsUpdateWaitingRestart Then UpdateRestart(False)
         If ReturnCode = Result.Exception Then
             If Not IsLogShown Then
                 FeedbackInfo()
                 Log("请在 https://jinshuju.net/f/rP4b6E?x_field_1=crash 提交错误报告,以便于作者解决此问题!")
                 IsLogShown = True
-                ShellAndGetExitCode(Path & "PCL\Log1.txt")
+                ShellOnly(Path & "PCL\Log1.txt")
             End If
             Thread.Sleep(500) '防止 PCL 在记事本打开前就被掐掉
         End If
@@ -841,7 +726,7 @@ Reopen:
                                        '获取并检查目标版本
                                        Dim TargetVersion As McVersion = McVersionCurrent
                                        If PageCurrent = PageType.VersionSetup Then TargetVersion = PageVersionLeft.Version
-                                       If PageCurrent = PageType.VersionSelect OrElse TargetVersion Is Nothing OrElse Not TargetVersion.Version.Modable Then
+                                       If PageCurrent = PageType.VersionSelect OrElse TargetVersion Is Nothing OrElse Not TargetVersion.Modable Then
                                            '正在选择版本,或当前版本不能安装 Mod
                                            Hint("若要安装 Mod,请先选择一个可以安装 Mod 的版本!")
                                        ElseIf Not (PageCurrent = PageType.VersionSetup AndAlso PageCurrentSub = PageSubType.VersionMod) Then
@@ -1039,11 +924,11 @@ Install:
         SetupUI = 1
         SetupSystem = 2
         SetupLink = 3
-        LinkCato = 2
-        LinkIoi = 3
-        LinkSetup = 5
-        LinkHelp = 6
-        LinkFeedback = 7
+        LinkHiper = 1
+        LinkIoi = 2
+        LinkSetup = 4
+        LinkHelp = 5
+        LinkFeedback = 6
         OtherHelp = 0
         OtherAbout = 1
         OtherTest = 2
@@ -1281,7 +1166,6 @@ Install:
                     PageChangeAnim(FrmDownloadLeft, FrmDownloadLeft.PageGet(SubType))
                 Case PageType.Link '联机
                     If FrmLinkLeft Is Nothing Then FrmLinkLeft = New PageLinkLeft
-                    If FrmLinkIoi Is Nothing Then FrmLinkIoi = New PageLinkIoi
                     PageChangeAnim(FrmLinkLeft, FrmLinkLeft.PageGet(SubType))
                 Case PageType.Setup '设置
                     If FrmSetupLeft Is Nothing Then FrmSetupLeft = New PageSetupLeft
@@ -1337,7 +1221,6 @@ Install:
         AniStart({
             AaCode(Sub()
                        AniControlEnabled += 1
-                       CType(PanMainRight.Child, MyPageRight).PageOnForceExit()
                        '把新页面添加进容器
                        PanMainLeft.Child = PageLeft
                        PageLeft.Opacity = 0

+ 2 - 2
Plain Craft Launcher 2/Modules/Base/ModAnimation.vb

@@ -777,12 +777,12 @@ Public Module ModAnimation
                                Log("[Animation] 动画线程开始")
                                Do While True
                                    '两帧之间的间隔时间
-                                   Dim DeltaTime As Long = MathRange(GetTimeTick() - AniLastTick, 0, 10000)
+                                   Dim DeltaTime As Long = MathRange(GetTimeTick() - AniLastTick, 0, 100000)
                                    If DeltaTime < 3 Then GoTo Sleeper
                                    AniLastTick = GetTimeTick()
                                    '记录 FPS
                                    If ModeDebug Then
-                                       If MathRange(AniLastTick - AniFPSTimer, 0, 10000) >= 500 Then
+                                       If MathRange(AniLastTick - AniFPSTimer, 0, 100000) >= 500 Then
                                            AniFPS = AniFPSCounter
                                            AniFPSCounter = 0
                                            AniFPSTimer = AniLastTick

+ 77 - 27
Plain Craft Launcher 2/Modules/Base/ModBase.vb

@@ -10,12 +10,12 @@ Public Module ModBase
 #Region "声明"
 
     '下列版本信息由更新器自动修改
-    Public Const VersionBaseName As String = "2.3.5" '不含分支前缀的显示用版本名
-    Public Const VersionStandardCode As String = "2.3.5." & VersionBranchCode '标准格式的四段式版本号
+    Public Const VersionBaseName As String = "2.4.0" '不含分支前缀的显示用版本名
+    Public Const VersionStandardCode As String = "2.4.0." & VersionBranchCode '标准格式的四段式版本号
 #If BETA Then
     Public Const VersionCode As Integer = 265 'Release
 #Else
-    Public Const VersionCode As Integer = 264 'Snapshot
+    Public Const VersionCode As Integer = 266 'Snapshot
 #End If
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -82,6 +82,10 @@ Public Module ModBase
     ''' 程序的缓存文件夹路径,以“\”结尾。
     ''' </summary>
     Public PathTemp As String = If(Setup.Get("SystemSystemCache") = "", IO.Path.GetTempPath() & "PCL\", Setup.Get("SystemSystemCache")).ToString.Replace("/", "\").TrimEnd("\") & "\"
+    ''' <summary>
+    ''' AppData 中的 PCL 文件夹路径,以“\”结尾。
+    ''' </summary>
+    Public PathAppdata As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\PCL\"
 
 #End Region
 
@@ -447,6 +451,32 @@ Public Module ModBase
 
 #Region "数学"
 
+    ''' <summary>
+    ''' 2~65 进制的转换。
+    ''' </summary>
+    Public Function RadixConvert(Input As String, FromRadix As Integer, ToRadix As Integer) As String
+        Const Digits As String = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/+="
+        '零与负数的处理
+        If String.IsNullOrEmpty(Input) Then Return "0"
+        Dim IsNegative As Boolean = Input.StartsWith("-")
+        If IsNegative Then Input = Input.TrimStart("-")
+        '转换为十进制
+        Dim RealNum As Long = 0, Scale As Long = 1
+        For Each Digit In Input.Reverse.Select(Function(l) Digits.IndexOf(l))
+            RealNum += Digit * Scale
+            Scale *= FromRadix
+        Next
+        '转换为指定进制
+        Dim Result = ""
+        While RealNum > 0
+            Dim NewNum As Integer = RealNum Mod ToRadix
+            RealNum = (RealNum - NewNum) / ToRadix
+            Result = Digits(NewNum) & Result
+        End While
+        '负数的结束处理与返回
+        Return If(IsNegative, "-", "") & Result
+    End Function
+
     ''' <summary>
     ''' 计算二阶贝塞尔曲线。
     ''' </summary>
@@ -1039,14 +1069,14 @@ Re:
             Next
             Return Result.ToString
         Catch ex As Exception
-            If Not Retry Then
+            If Retry OrElse TypeOf ex Is FileNotFoundException Then
+                Log(ex, "获取文件 MD5 失败:" & FilePath)
+                Return ""
+            Else
                 Retry = True
                 Log(ex, "获取文件 MD5 可重试失败:" & FilePath, LogLevel.Normal)
                 Thread.Sleep(RandomInteger(200, 500))
                 GoTo Re
-            Else
-                Log(ex, "获取文件 MD5 失败:" & FilePath)
-                Return ""
             End If
         End Try
     End Function
@@ -1070,14 +1100,14 @@ Re:
             Next
             Return Result.ToString
         Catch ex As Exception
-            If Not Retry Then
+            If Retry OrElse TypeOf ex Is FileNotFoundException Then
+                Log(ex, "获取文件 SHA256 失败:" & FilePath)
+                Return ""
+            Else
                 Retry = True
                 Log(ex, "获取文件 SHA256 可重试失败:" & FilePath, LogLevel.Normal)
                 Thread.Sleep(RandomInteger(200, 500))
                 GoTo Re
-            Else
-                Log(ex, "获取文件 SHA256 失败:" & FilePath)
-                Return ""
             End If
         End Try
     End Function
@@ -1101,14 +1131,14 @@ Re:
             Next
             Return Result.ToString
         Catch ex As Exception
-            If Not Retry Then
+            If Retry OrElse TypeOf ex Is FileNotFoundException Then
+                Log(ex, "获取文件 SHA1 失败:" & FilePath)
+                Return ""
+            Else
                 Retry = True
                 Log(ex, "获取文件 SHA1 可重试失败:" & FilePath, LogLevel.Normal)
                 Thread.Sleep(RandomInteger(200, 500))
                 GoTo Re
-            Else
-                Log(ex, "获取文件 SHA1 失败:" & FilePath)
-                Return ""
             End If
         End Try
     End Function
@@ -1175,7 +1205,7 @@ Re:
                         If Hash <> GetAuthMD5(LocalPath) Then Return "文件 MD5 应为 " & Hash & ",实际为 " & GetAuthMD5(LocalPath)
                     ElseIf Hash.Length = 64 Then 'SHA256
                         If Hash <> GetAuthSHA256(LocalPath) Then Return "文件 SHA256 应为 " & Hash & ",实际为 " & GetAuthSHA256(LocalPath)
-                    Else 'SHA1
+                    Else 'SHA1 (40)
                         If Hash <> GetAuthSHA1(LocalPath) Then Return "文件 SHA1 应为 " & Hash & ",实际为 " & GetAuthSHA1(LocalPath)
                     End If
                 End If
@@ -1803,28 +1833,36 @@ NextElement:
         End Sub
     End Class
 
+    ''' <summary>
+    ''' 前台运行文件。
+    ''' </summary>
+    ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
+    ''' <param name="Arguments">运行参数。</param>
+    Public Sub ShellOnly(FileName As String, Optional Arguments As String = "")
+        Using Program As New Process
+            Program.StartInfo.Arguments = Arguments
+            Program.StartInfo.FileName = FileName
+            Log("[System] 执行外部命令:" & FileName & " " & Arguments)
+            Program.Start()
+        End Using
+    End Sub
     ''' <summary>
     ''' 前台运行文件并返回返回值。
     ''' </summary>
     ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
     ''' <param name="Arguments">运行参数。</param>
-    ''' <param name="WaitForExit">是否等待该程序结束。默认为 False。</param>
     ''' <param name="Timeout">等待该程序结束的最长时间(毫秒)。超时会返回 Result.Timeout。</param>
-    Public Function ShellAndGetExitCode(FileName As String, Optional Arguments As String = "", Optional WaitForExit As Boolean = False, Optional Timeout As Integer = 1000000) As Result
+    Public Function ShellAndGetExitCode(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000) As Result
         Try
             Using Program As New Process
                 Program.StartInfo.Arguments = Arguments
                 Program.StartInfo.FileName = FileName
                 Log("[System] 执行外部命令并等待返回码:" & FileName & " " & Arguments)
                 Program.Start()
-                If WaitForExit Then
-                    If Program.WaitForExit(Timeout) Then
-                        Return Program.ExitCode
-                    Else
-                        Return Result.Timeout
-                    End If
+                If Program.WaitForExit(Timeout) Then
+                    Return Program.ExitCode
                 Else
-                    Return Result.Success
+                    Return Result.Timeout
                 End If
             End Using
         Catch ex As Exception
@@ -1909,6 +1947,18 @@ NextElement:
     ''' 如果当前并非 UI 线程,则会阻断当前线程,直至 UI 线程执行完毕。
     ''' 为防止线程互锁,请仅在开始加载动画、从 UI 获取输入时使用!
     ''' </summary>
+    Public Function RunInUiWait(Of Output)(Action As Func(Of Output)) As Output
+        If RunInUi() Then
+            Return Action()
+        Else
+            Return Application.Current.Dispatcher.Invoke(Action)
+        End If
+    End Function
+    ''' <summary>
+    ''' 确保在 UI 线程中执行代码。
+    ''' 如果当前并非 UI 线程,则会阻断当前线程,直至 UI 线程执行完毕。
+    ''' 为防止线程互锁,请仅在开始加载动画、从 UI 获取输入时使用!
+    ''' </summary>
     Public Sub RunInUiWait(Action As Action)
         If RunInUi() Then
             Action()
@@ -2003,11 +2053,11 @@ NextElement:
         End Try
     End Sub
     ''' <summary>
-    ''' 打开 explorer。
+    ''' 打开 explorer。注意参数中的路径要尽量加上双引号!
     ''' </summary>
     Public Sub OpenExplorer(Argument As String)
         Try
-            Process.Start("explorer.exe", Argument)
+            ShellOnly("explorer.exe", Argument)
         Catch ex As Exception
             Log(ex, "打开资源管理器失败,请尝试关闭安全软件(如 360 安全卫士)", LogLevel.Msgbox)
         End Try

+ 1 - 2
Plain Craft Launcher 2/Modules/Base/ModLoader.vb

@@ -418,7 +418,7 @@
                     Loader.State = LoadState.Waiting
                 Next
             End If
-            Update()
+            RunInThread(AddressOf Update)
         End Sub
         Public Overrides Sub Abort()
             '改变状态
@@ -484,7 +484,6 @@
                     Case LoadState.Finished
                         '检查是否需要重启
                         If Loader.GetType.Name.StartsWith("LoaderTask") Then '类型名后面带有泛型,必须用 StartsWith
-                            'UNDONE: 这里的 IgnoreReloadTimeout 导致加载器不会重启,也就是微软登录不会自动重新登录
                             If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
                                 If ModeDebug Then Log("[Loader] 由于输入条件变更,重启已完成的加载器 " & Loader.Name)
                                 GoTo Restart

+ 21 - 2
Plain Craft Launcher 2/Modules/Base/ModNet.vb

@@ -1,9 +1,28 @@
 Imports System.Net
 
 Public Module ModNet
-
     Public Const NetDownloadEnd As String = ".PCLDownloading"
 
+    ''' <summary>
+    ''' 测试 Ping。失败则返回 -1。
+    ''' </summary>
+    Public Function Ping(Ip As String, Optional Timeout As Integer = 10000, Optional MakeLog As Boolean = True) As Integer
+        Dim PingResult As NetworkInformation.PingReply
+        Try
+            PingResult = (New NetworkInformation.Ping).Send(Ip)
+        Catch ex As Exception
+            If MakeLog Then Log("[Net] Ping " & Ip & " 失败:" & ex.Message)
+            Return -1
+        End Try
+        If PingResult.Status = NetworkInformation.IPStatus.Success Then
+            If MakeLog Then Log("[Net] Ping " & Ip & " 结束:" & PingResult.RoundtripTime & "ms")
+            Return PingResult.RoundtripTime
+        Else
+            If MakeLog Then Log("[Net] Ping " & Ip & " 失败")
+            Return -1
+        End If
+    End Function
+
     ''' <summary>
     ''' 以 WebClient 获取网页源代码。会进行至多 45 秒 3 次的尝试,允许最长 30s 的超时。
     ''' </summary>
@@ -940,7 +959,7 @@ Capture:
                     Next
                     '是否禁用多线程,以及规定碎片大小
                     Dim TargetUrl As String = GetSource().Url
-                    If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("gitcode.net") Then Return False
+                    If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("gitcode.net") OrElse TargetUrl.Contains("mcer.cn") Then Return False
                     Dim RealFilePieceLimit = If(TargetUrl.Contains("download.mcbbs.net"), FilePieceLimit * 5, FilePieceLimit)
                     '寻找最大碎片
                     Dim FilePieceMax As NetThread = Threads

+ 2 - 2
Plain Craft Launcher 2/Modules/Base/ModValidate.vb

@@ -49,10 +49,10 @@ End Class
 Public Class ValidateRegex
     Inherits Validate
     Public Property Regex As String
-    Public Property ErrorDescription As String = ""
+    Public Property ErrorDescription As String = "正则检查失败!"
     Public Sub New()
     End Sub '用于 XAML 初始化
-    Public Sub New(Regex As String, Optional ErrorDescription As String = "")
+    Public Sub New(Regex As String, Optional ErrorDescription As String = "正则检查失败!")
         Me.Regex = Regex
         Me.ErrorDescription = ErrorDescription
     End Sub

+ 19 - 7
Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb

@@ -9,7 +9,7 @@
         SyncLock IsAnalyzeCacheClearedLock
             If Not IsAnalyzeCacheCleared Then
                 Try
-                    DeleteDirectory(PathTemp & "CrashAnalyzer")
+                    DeleteDirectory(PathTemp & "CrashAnalyzer\")
                 Catch ex As Exception
                     Log(ex, "清理崩溃分析缓存失败")
                 End Try
@@ -153,6 +153,7 @@
         ExtraLog
         CrashReport
     End Enum
+    Private DirectFile As KeyValuePair(Of String, String())? = Nothing '在弹窗中选择直接打开的文件
     ''' <summary>
     ''' 从 AnalyzeRawFiles 中提取实际有用的文本片段存储到 AnalyzeFiles,并整理可用于生成报告的文件。
     ''' 返回找到的用于分析的项目数。
@@ -161,18 +162,22 @@
         Log("[Crash] 步骤 2:准备日志文本")
 
         '对日志文件进行分类
+        DirectFile = Nothing
         Dim TotalFiles As New List(Of KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String())))
         For Each LogFile In AnalyzeRawFiles
             Dim FileName As String = GetFileNameFromPath(LogFile.Key)
             Dim TargetType As AnalyzeFileType
             If FileName.StartsWith("hs_err") Then
                 TargetType = AnalyzeFileType.HsErr
+                DirectFile = LogFile
             ElseIf FileName.StartsWith("crash-") Then
                 TargetType = AnalyzeFileType.CrashReport
+                DirectFile = LogFile
             ElseIf FileName = "latest.log" OrElse FileName = "latest log.txt" OrElse
                    FileName = "debug.log" OrElse FileName = "debug log.txt" OrElse FileName = "游戏崩溃前的输出.txt" OrElse
                    FileName = "rawoutput.log" OrElse FileName = "启动器日志.txt" OrElse FileName = "PCL2 启动器日志.txt" OrElse FileName = "log1.txt" Then
                 TargetType = AnalyzeFileType.MinecraftLog
+                If DirectFile Is Nothing Then DirectFile = LogFile
             ElseIf FileName.EndsWith(".log") OrElse FileName.EndsWith(".txt") Then
                 TargetType = AnalyzeFileType.ExtraLog
             Else
@@ -689,8 +694,19 @@ NextStack:
     Public Sub Output(IsHandAnalyze As Boolean, Optional ExtraFiles As List(Of String) = Nothing)
         '弹窗提示
         FrmMain.ShowWindowToTop()
-        Select Case MyMsgBox(GetAnalyzeResult(IsHandAnalyze), If(IsHandAnalyze, "错误报告分析结果", "Minecraft 出现错误"), "确定", If(IsHandAnalyze, "", "导出错误报告"))
+Redo:
+        Select Case MyMsgBox(GetAnalyzeResult(IsHandAnalyze), If(IsHandAnalyze, "错误报告分析结果", "Minecraft 出现错误"),
+                             "确定", If(IsHandAnalyze OrElse DirectFile Is Nothing, "", "查看日志"), If(IsHandAnalyze, "", "导出错误报告"))
             Case 2
+                If File.Exists(DirectFile.Value.Key) Then
+                    ShellOnly(DirectFile.Value.Key)
+                Else
+                    Dim FilePath As String = PathTemp & "Crash.txt"
+                    WriteFile(FilePath, Join(DirectFile.Value.Value, vbCrLf))
+                    ShellOnly(FilePath)
+                End If
+                GoTo Redo
+            Case 3
                 Dim FileAddress As String = Nothing
                 Try
                     '获取文件路径
@@ -723,11 +739,7 @@ NextStack:
                     Log(ex, "导出错误报告失败", LogLevel.Feedback)
                     Exit Sub
                 End Try
-                Try
-                    Process.Start("explorer", "/select," & FileAddress)
-                Catch ex As Exception
-                    Log(ex, "打开错误报告的存放文件夹失败")
-                End Try
+                OpenExplorer("/select,""" & FileAddress & """")
         End Select
     End Sub
     ''' <summary>

+ 16 - 5
Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb

@@ -1217,7 +1217,7 @@
     ''' CurseForge 工程列表获取事件。
     ''' </summary>
     Public Sub DlCfProjectSub(Task As LoaderTask(Of DlCfProjectRequest, DlCfProjectResult))
-        Dim RawFilter As String = If(Task.Input.SearchFilter, "")
+        Dim RawFilter As String = If(Task.Input.SearchFilter, "").Trim.ToLower
         Task.Input.SearchFilter = RawFilter
         Log("[Download] CurseForge 工程列表搜索原始文本:" & RawFilter)
         '中文请求关键字处理
@@ -1263,12 +1263,20 @@
         Dim RightKeywords As New List(Of String)
         For Each Keyword In AllPossibleKeywords.Split(" ")
             If Keyword.Trim = "" Then Continue For
+            If Keyword = "forge" OrElse Keyword = "fabric" OrElse Keyword = "for" Then 'https://github.com/Hex-Dragon/PCL2/issues/208
+                Log("[Download] 已跳过搜索关键词 " & Keyword, LogLevel.Developer)
+                Continue For
+            End If
             RightKeywords.Add(Keyword)
         Next
-        Task.Input.SearchFilter = Join(ArrayNoDouble(RightKeywords), " ").ToLower
+        If RawFilter.Length > 0 AndAlso RightKeywords.Count = 0 Then
+            Task.Input.SearchFilter = RawFilter '全都被过滤掉了
+        Else
+            Task.Input.SearchFilter = Join(ArrayNoDouble(RightKeywords), " ").ToLower
+        End If
         '例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod)
-        If RawFilter.ToLower.Contains("optiforge") Then Task.Input.SearchFilter = "optiforge"
-        If RawFilter.ToLower.Contains("optifabric") Then Task.Input.SearchFilter = "optifabric"
+        If RawFilter.Replace(" ", "").ToLower.Contains("optiforge") Then Task.Input.SearchFilter = "optiforge"
+        If RawFilter.Replace(" ", "").ToLower.Contains("optifabric") Then Task.Input.SearchFilter = "optifabric"
         Log("[Download] CurseForge 工程列表搜索最终文本:" & Task.Input.SearchFilter, LogLevel.Developer)
         '正式获取
         Dim Url = Task.Input.GetAddress()
@@ -1505,7 +1513,6 @@
                 Json = NetGetCodeByRequestRetry("https://api.curseforge.com/v1/mods/" & ProjectId & "/files?pageSize=999", Accept:="application/json", IsJson:=True)("data")
             Else
                 'Mod 使用每个版本最新的文件
-                'TODO: 完整 Mod 文件列表支持
                 Json = GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(TargetProject.FileIndexes, ",") & "]}", "application/json"))("data")
             End If
             Dim Files As New Dictionary(Of Integer, DlCfFile)
@@ -1633,6 +1640,10 @@ ExitSub:
             If WaitCycle = 0 Then
                 '启动第一个源
                 LoaderList(0).Key.Start(MainLoader.Input, IsForceRestart)
+                '将其他源标记为未启动,以确保可以切换下载源(#184)
+                For i = 1 To LoaderList.Count - 1
+                    LoaderList(i).Key.State = LoadState.Waiting
+                Next
             End If
             For i = 0 To LoaderList.Count - 1
                 If WaitCycle = LoaderList(i).Value * 100 Then

+ 22 - 17
Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb

@@ -15,7 +15,7 @@ Public Module ModLaunch
     End Sub
 
     '启动状态切换
-    Public McLaunchLoader As New LoaderTask(Of String, Object)("Loader Launch", AddressOf McLaunchStart) With {.OnStateChanged = AddressOf McLaunchState, .ReloadTimeout = 1}
+    Public McLaunchLoader As New LoaderTask(Of String, Object)("Loader Launch", AddressOf McLaunchStart) With {.OnStateChanged = AddressOf McLaunchState}
     Public McLaunchLoaderReal As LoaderCombo(Of Object)
     Public McLaunchProcess As Process
     Public McLaunchWatcher As Watcher
@@ -206,12 +206,13 @@ NextInner:
         Public AccessToken As String = ""
         Public Uuid As String = ""
         Public UserName As String = ""
+        Public ProfileJson As String = ""
 
         Public Sub New()
             Type = McLoginType.Ms
         End Sub
         Public Overrides Function GetHashCode() As Integer
-            Return GetHash(OAuthRefreshToken & AccessToken & Uuid & UserName) Mod Integer.MaxValue
+            Return GetHash(OAuthRefreshToken & AccessToken & Uuid & UserName & ProfileJson) Mod Integer.MaxValue
         End Function
     End Class
     Public Class McLoginLegacy
@@ -419,7 +420,8 @@ NextInner:
         Data.Progress = 0.05
         '检查是否已经登录完成
         If Input.AccessToken <> "" AndAlso Not Data.IsForceRestarting Then
-            Data.Output = New McLoginResult With {.AccessToken = Input.AccessToken, .Name = Input.UserName, .Uuid = Input.Uuid, .Type = "Microsoft", .ClientToken = Input.Uuid}
+            Data.Output = New McLoginResult With
+                {.AccessToken = Input.AccessToken, .Name = Input.UserName, .Uuid = Input.Uuid, .Type = "Microsoft", .ClientToken = Input.Uuid, .ProfileJson = Input.ProfileJson}
             GoTo SkipLogin
         End If
         '尝试登录
@@ -456,6 +458,7 @@ Relogin:
         Setup.Set("CacheMsAccess", AccessToken)
         Setup.Set("CacheMsUuid", Result(0))
         Setup.Set("CacheMsName", Result(1))
+        Setup.Set("CacheMsProfileJson", Result(2))
         Data.Output = New McLoginResult With {.AccessToken = AccessToken, .Name = Result(1), .Uuid = Result(0), .Type = "Microsoft", .ClientToken = Result(0), .ProfileJson = Result(2)}
         '解锁主题
 SkipLogin:
@@ -1076,6 +1079,18 @@ SystemBrowser:
 
     Private McLaunchArgument As String
 
+    ''' <summary>
+    ''' 释放 Java Wrapper 并返回文件路径。
+    ''' </summary>
+    Public Function ExtractJavaWrapper() As String
+        Dim WrapperPath As String = PathAppdata & "JavaWrapper.jar"
+        If Not File.Exists(WrapperPath) OrElse New FileInfo(WrapperPath).Length > 20 * 1024 Then
+            WriteFile(WrapperPath, GetResources("JavaWrapper"))
+            McLaunchLog("已自动释放 Java Wrapper")
+        End If
+        Return WrapperPath
+    End Function
+
     '主方法,合并 Jvm、Game、Replace 三部分的参数数据
     Private Sub McLaunchArgumentMain(Loader As LoaderTask(Of String, List(Of McLibToken)))
         McLaunchLog("开始获取 Minecraft 启动参数")
@@ -1175,12 +1190,7 @@ SystemBrowser:
 
         '添加 Java Wrapper 作为主 Jar
         If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
-        Dim WrapperPath As String = PathAppdata & "JavaWrapper.jar"
-        If Not File.Exists(WrapperPath) Then
-            WriteFile(WrapperPath, GetResources("JavaWrapper"))
-            McLaunchLog("已自动释放 Java Wrapper")
-        End If
-        DataList.Add("-jar """ & WrapperPath & """")
+        DataList.Add("-jar """ & ExtractJavaWrapper() & """")
 
         '添加 MainClass
         If Version.JsonObject("mainClass") Is Nothing Then
@@ -1244,12 +1254,7 @@ NextVersion:
 
         '添加 Java Wrapper 作为主 Jar
         If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
-        Dim WrapperPath As String = PathAppdata & "JavaWrapper.jar"
-        If Not File.Exists(WrapperPath) Then
-            WriteFile(WrapperPath, GetResources("JavaWrapper"))
-            McLaunchLog("已自动释放 Java Wrapper")
-        End If
-        DataList.Add("-jar """ & WrapperPath & """")
+        DataList.Add("-jar """ & ExtractJavaWrapper() & """")
 
         '将 "-XXX" 与后面 "XXX" 合并到一起
         '如果不合并,会导致 Forge 1.17 启动无效,它有两个 --add-exports,进一步导致其中一个在后面被去重
@@ -1619,7 +1624,7 @@ NextVersion:
             Directory.CreateDirectory(McVersionCurrent.PathIndie & "resourcepacks\")
             Dim ZipFileAddress As String = McVersionCurrent.PathIndie & "resourcepacks\PCL2 Skin.zip"
             Dim NewTypeSetup As Boolean = McVersionCurrent.Version.McCodeMain >= 13 OrElse McVersionCurrent.Version.McCodeMain < 6
-            If McLoginLoader.Input.Type = McLoginType.Legacy AndAlso Setup.Get("LaunchSkinType") = 4 AndAlso File.Exists(PathTemp & "CustomSkin.png") Then
+            If McLoginLoader.Input.Type = McLoginType.Legacy AndAlso Setup.Get("LaunchSkinType") = 4 AndAlso File.Exists(PathAppdata & "CustomSkin.png") Then
                 Directory.CreateDirectory(PathTemp)
                 Dim MetaFileAddress As String = PathTemp & "pack.mcmeta"
                 Dim PackPicAddress As String = PathTemp & "pack.png"
@@ -1659,7 +1664,7 @@ NextVersion:
                 Dim Bit As New MyBitmap(PathImage & "Heads/Logo.png")
                 Bit.Save(PackPicAddress)
                 WriteFile(MetaFileAddress, "{""pack"":{""pack_format"":" & PackFormat & ",""description"":""PCL2 自定义离线皮肤资源包""}}")
-                Dim Skin As New MyBitmap(PathTemp & "CustomSkin.png")
+                Dim Skin As New MyBitmap(PathAppdata & "CustomSkin.png")
                 If (McVersionCurrent.Version.McCodeMain = 6 OrElse McVersionCurrent.Version.McCodeMain = 7) AndAlso Skin.Pic.Height = 64 Then
                     McLaunchLog("该 Minecraft 版本不支持双层皮肤,已进行裁剪")
                     Skin = Skin.Clip(New System.Drawing.Rectangle(0, 0, 64, 32))

+ 12 - 13
Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb

@@ -404,7 +404,6 @@ Wait:
             Log(ex, "遍历查找 Java 时出错(" & OriginalPath.FullName & ")")
         End Try
     End Sub
-    Public PathAppdata As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\PCL\"
 
     '获取
 
@@ -837,7 +836,7 @@ UserPass:
         ''' </summary>
         Public ReadOnly Property PathIndie As String
             Get
-                Return GetPathIndie(Version.Modable)
+                Return GetPathIndie(Modable)
             End Get
         End Property
         ''' <summary>
@@ -917,6 +916,15 @@ UserPass:
         ''' </summary>
         Public DisplayType As McVersionCardType = McVersionCardType.Auto
         ''' <summary>
+        ''' 该版本是否可以安装 Mod。
+        ''' </summary>
+        Public ReadOnly Property Modable As Boolean
+            Get
+                Return Version.HasFabric OrElse Version.HasForge OrElse Version.HasLiteLoader OrElse
+                    DisplayType = McVersionCardType.API '#223
+            End Get
+        End Property
+        ''' <summary>
         ''' 版本信息。
         ''' </summary>
         Public Property Version As McVersionInfo
@@ -1009,7 +1017,7 @@ UserPass:
                             _Version.McName = Regex
                             GoTo VersionSearchFinish
                         End If
-                        'TODO: [Quilt 支持] 从 Quilt 版本中获取版本号
+                        'FUTURE: [Quilt 支持] 从 Quilt 版本中获取版本号
                         '从 jar 项中获取版本号
                         If JsonObject("jar") IsNot Nothing Then
                             _Version.McName = JsonObject("jar").ToString
@@ -1324,7 +1332,7 @@ Recheck:
                             Version.HasLiteLoader = True
                         End If
                         'Fabric、Forge
-                        'TODO: [Quilt 支持] 确认这里的玩意儿对不对
+                        'FUTURE: [Quilt 支持] 确认这里的玩意儿对不对
                         If RealJson.Contains("net.fabricmc:fabric-loader") OrElse RealJson.Contains("org.quiltmc:quilt-loader") Then
                             State = McVersionState.Fabric
                             Version.FabricVersion = If(RegexSeek(RealJson, "(?<=(net.fabricmc:fabric-loader:)|(org.quiltmc:quilt-loader:))[0-9\.]+(\+build.[0-9]+)?"), "未知版本").Replace("+build", "")
@@ -1511,15 +1519,6 @@ ExitDataLoad:
 
         'API
 
-        ''' <summary>
-        ''' 该版本是否可以安装 Mod。
-        ''' </summary>
-        Public ReadOnly Property Modable As Boolean
-            Get
-                Return HasFabric OrElse HasForge OrElse HasLiteLoader
-            End Get
-        End Property
-
         ''' <summary>
         ''' 生成对此版本信息的用户友好的描述性字符串。
         ''' </summary>

+ 2 - 2
Plain Craft Launcher 2/My Project/AssemblyInfo.vb

@@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices
 ' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
 ' 方法是按如下所示使用“*”
 
-<Assembly: AssemblyVersion("2.3.5.0")>
-<Assembly: AssemblyFileVersion("2.3.5.0")>
+<Assembly: AssemblyVersion("2.4.0.0")>
+<Assembly: AssemblyFileVersion("2.4.0.0")>
 <Assembly: NeutralResourcesLanguage("")>

+ 0 - 10
Plain Craft Launcher 2/My Project/Resources.Designer.vb

@@ -140,16 +140,6 @@ Namespace My.Resources
             End Get
         End Property
         
-        '''<summary>
-        '''  查找 System.Byte[] 类型的本地化资源。
-        '''</summary>
-        Friend ReadOnly Property STUN() As Byte()
-            Get
-                Dim obj As Object = ResourceManager.GetObject("STUN", resourceCulture)
-                Return CType(obj,Byte())
-            End Get
-        End Property
-        
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>

+ 0 - 3
Plain Craft Launcher 2/My Project/Resources.resx

@@ -142,9 +142,6 @@
   <data name="NAudio" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\NAudio.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="STUN" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\STUN.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
   <data name="Transformer" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\transformer.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>

+ 10 - 14
Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb

@@ -378,12 +378,7 @@ Public Module ModDownloadLib
             Throw New Exception("未找到用于安装 OptiFine 的 Java")
         End If
         '添加 Java Wrapper 作为主 Jar
-        Dim WrapperPath As String = PathAppdata & "JavaWrapper.jar"
-        If Not File.Exists(WrapperPath) Then
-            WriteFile(WrapperPath, GetResources("JavaWrapper"))
-            McLaunchLog("已自动释放 Java Wrapper")
-        End If
-        Dim Arguments = "-Duser.home=""" & BaseMcFolderHome & """ -cp """ & Target & """ -jar """ & WrapperPath & """ optifine.Installer"
+        Dim Arguments = "-Duser.home=""" & BaseMcFolderHome & """ -cp """ & Target & """ -jar """ & ExtractJavaWrapper() & """ optifine.Installer"
         If Java.VersionCode >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments
         '开始启动
         SyncLock InstallSyncLock
@@ -528,7 +523,10 @@ Public Module ModDownloadLib
                                                                              '等待原版文件下载完成
                                                                              For Each Loader In ClientDownloadLoader.GetLoaderList
                                                                                  If Loader.Name <> McDownloadClientLibName Then Continue For
-                                                                                 Loader.WaitForExit()
+                                                                                 If Loader.State = LoadState.Loading Then
+                                                                                     Log("[Download] OptiFine 安装正在等待原版文件下载完成")
+                                                                                     Loader.WaitForExit()
+                                                                                 End If
                                                                                  Exit For
                                                                              Next
                                                                              '拷贝原版文件
@@ -1094,12 +1092,7 @@ Public Module ModDownloadLib
             Throw New Exception("未找到用于安装 Forge 的 Java")
         End If
         '添加 Java Wrapper 作为主 Jar
-        Dim WrapperPath As String = PathAppdata & "JavaWrapper.jar"
-        If Not File.Exists(WrapperPath) Then
-            WriteFile(WrapperPath, GetResources("JavaWrapper"))
-            McLaunchLog("已自动释放 Java Wrapper")
-        End If
-        Dim Arguments = "-cp """ & PathTemp & "Cache\forge_installer.jar;" & Target & """ -jar """ & WrapperPath & """  com.bangbang93.ForgeInstaller """ & McFolder
+        Dim Arguments = "-cp """ & PathTemp & "Cache\forge_installer.jar;" & Target & """ -jar """ & ExtractJavaWrapper() & """  com.bangbang93.ForgeInstaller """ & McFolder
         If Java.VersionCode >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments
         '开始启动
         SyncLock InstallSyncLock
@@ -1339,7 +1332,10 @@ Public Module ModDownloadLib
                                                                              '等待原版文件下载完成
                                                                              For Each Loader In ClientDownloadLoader.GetLoaderList
                                                                                  If Loader.Name <> McDownloadClientLibName Then Continue For
-                                                                                 Loader.WaitForExit()
+                                                                                 If Loader.State = LoadState.Loading Then
+                                                                                     Log("[Download] Forge 安装正在等待原版文件下载完成")
+                                                                                     Loader.WaitForExit()
+                                                                                 End If
                                                                                  Exit For
                                                                              Next
                                                                              '拷贝原版文件

+ 2 - 2
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCfDetail.xaml.vb

@@ -119,7 +119,7 @@
 #End Region
 
     Private IsFirstInit As Boolean = True
-    Public Sub Init() Handles Me.OnPageEnter
+    Public Sub Init() Handles Me.PageEnter
         AniControlEnabled += 1
         Project = FrmMain.PageCurrent.Additional
         PanBack.ScrollToHome()
@@ -200,7 +200,7 @@
                 DefaultFolder = DownloadFolder
                 If McVersionCurrent IsNot Nothing Then
                     If Not McVersionCurrent.IsLoaded Then McVersionCurrent.Load()
-                    If McVersionCurrent.Version.Modable Then
+                    If McVersionCurrent.Modable Then
                         DefaultFolder = McVersionCurrent.PathIndie & "mods\"
                         Directory.CreateDirectory(DefaultFolder)
                     End If

+ 3 - 3
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb

@@ -479,12 +479,12 @@ UseDefault:
                     End If
                 End Try
             Case 4 '自定义
-                If Not File.Exists(PathTemp & "CustomSkin.png") Then
+                If Not File.Exists(PathAppdata & "CustomSkin.png") Then
                     Hint("未找到离线皮肤自定义文件,可能它已被删除。PCL2 将使用默认的 Steve 皮肤!")
                     Setup.Set("LaunchSkinType", 1)
                     GoTo UseDefault
                 End If
-                Data.Output = PathTemp & "CustomSkin.png"
+                Data.Output = PathAppdata & "CustomSkin.png"
         End Select
         '刷新显示
         If FrmLoginLegacy IsNot Nothing Then
@@ -607,7 +607,7 @@ Finish:
     Public Sub LaunchButtonClick(Optional ServerIp As String = "")
         If BtnLaunch.IsEnabled AndAlso BtnLaunch.Visibility = Visibility.Visible AndAlso BtnLaunch.IsHitTestVisible Then
             If BtnLaunch.Text = "启动游戏" Then
-                McLaunchLoader.Start(ServerIp)
+                McLaunchLoader.Start(ServerIp, IsForceRestart:=True)
             ElseIf BtnLaunch.Text = "下载游戏" Then
                 FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadInstall)
             End If

+ 1 - 1
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMojangSkin.xaml.vb

@@ -88,7 +88,7 @@
                                    MySkin.ReloadCache("http://textures.minecraft.net/texture/" & Result, False)
                                ElseIf Result.ToLower.Contains("ip not secured") Then
                                    If MyMsgBox("首次操作需要在官网验证密保问题。" & vbCrLf & "验证完成后,你即可使用 PCL 更改皮肤,且无需再次验证。", "验证密保问题", "开始验证", "取消") = 1 Then
-                                       OpenWebsite("https://www.minecraft.net/zh-hans/msaprofile/mygames/editskin")
+                                       OpenWebsite("https://www.minecraft.net/zh-hans/profile/skin")
                                    End If
                                ElseIf Result.Contains("request requires user authentication") Then
                                    Hint("正在重新登录,请稍后再次尝试更改皮肤!")

+ 2 - 1
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb

@@ -20,7 +20,7 @@
     ''' </summary>
     Public Shared Function GetLoginData() As McLoginMs
         If McLoginMsLoader.State = LoadState.Finished Then
-            Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsOAuthRefresh"), .UserName = Setup.Get("CacheMsName"), .AccessToken = Setup.Get("CacheMsAccess"), .Uuid = Setup.Get("CacheMsUuid")}
+            Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsOAuthRefresh"), .UserName = Setup.Get("CacheMsName"), .AccessToken = Setup.Get("CacheMsAccess"), .Uuid = Setup.Get("CacheMsUuid"), .ProfileJson = Setup.Get("CacheMsProfileJson")}
         Else
             Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsOAuthRefresh"), .UserName = Setup.Get("CacheMsName")}
         End If
@@ -51,6 +51,7 @@
     Private Sub BtnExit_Click() Handles BtnExit.Click
         Setup.Set("CacheMsOAuthRefresh", "")
         Setup.Set("CacheMsAccess", "")
+        Setup.Set("CacheMsProfileJson", "")
         Setup.Set("CacheMsUuid", "")
         Setup.Set("CacheMsName", "")
         McLoginMsLoader.Abort()

+ 29 - 25
Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml

@@ -4,31 +4,35 @@
              xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
              mc:Ignorable="d" x:Class="PageLinkFeedback"
              d:DesignWidth="800" d:DesignHeight="800">
-    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
-        <local:MyCard Title="反馈途径选择">
-            <StackPanel Margin="20,40,18,18">
-                <local:MyListItem Title="联机模块反馈" Info="向联机模块开发者反馈联机失败等异常"
-                              Type="Clickable" EventType="打开网页" EventData="https://mcer.cn/"
-                              PaddingRight="20" Logo="pack://application:,,,/images/Blocks/RedstoneBlock.png" />
-                <local:MyListItem Title="违法违规举报" Info="投诉、举报联机使用者的各类违法违规行为"
+    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
+        <StackPanel Name="PanMain" Margin="25,25,25,10" Grid.IsSharedSizeScope="True">
+            <local:MyCard Title="关于" Margin="0,0,0,15">
+                <StackPanel Margin="20,40,18,18">
+                    <TextBlock TextWrapping="Wrap" Text="多人联机服务由速聚授权提供,PCL2 仅为速聚提供多人联机服务入口,使用中遇到的任何问题由速聚负责处理。你在使用多人联机服务过程中所遇到的任何问题与纠纷(包括其付费业务)均与 PCL2 无关,应与速聚协商解决。" 
+                           Margin="0,0,0,3" />
+                    <TextBlock TextWrapping="Wrap" Text="PCL2 作者并不会从速聚的付费业务中获得任何分成、奖金等报酬,请不要瞎说。" 
+                           Margin="0,0,0,6" />
+                    <local:MyListItem Title="速聚官网" Info="打开速聚的官方网站"
+                              Type="Clickable" EventType="打开网页" EventData="https://www.the.bb/zh-cn"
+                              PaddingRight="20" Logo="pack://application:,,,/images/Blocks/Grass.png" />
+                    <local:MyListItem Title="授权协议书" Info="查看速聚与 PCL2 间的授权协议书"
+                              Type="Clickable" EventType="打开网页" EventData="https://shimo.im/docs/0l3NVXYzeXhb903R"
+                              PaddingRight="20" Logo="pack://application:,,,/images/Blocks/Grass.png" />
+                    <local:MyListItem Title="用户协议与隐私政策" Info="查看速聚的用户协议与隐私政策文档"
+                              Type="Clickable" EventType="打开网页" EventData="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzkxMTMyODk3Mg==&amp;action=getalbum&amp;album_id=2585385685407514625&amp;scene=173&amp;from_msgid=2247483720&amp;from_itemidx=1&amp;count=3&amp;nolastread=1#wechat_redirect"
+                              PaddingRight="20" Logo="pack://application:,,,/images/Blocks/Grass.png" />
+                </StackPanel>
+            </local:MyCard>
+            <local:MyCard Title="发送反馈" Margin="0,0,0,15">
+                <StackPanel Margin="20,40,18,18">
+                    <local:MyListItem Title="联机反馈与交流" Info="加入联机模块官方 QQ 群反馈联机异常,或询问联机功能的使用方式"
+                              Type="Clickable" PaddingRight="20" Logo="pack://application:,,,/images/Blocks/RedstoneBlock.png"
+                              EventType="打开网页" EventData="https://shimowendang.com/docs/KrkEVXz1K0frowAJ#anchor-7qQG" />
+                    <local:MyListItem Title="违法违规举报" Info="投诉、举报联机使用者的各类违法违规行为"
                               Type="Clickable" EventType="打开网页" EventData="https://shimo.im/docs/rGrd8pY8xWkt6ryW#anchor-7x6P"
                               PaddingRight="20" Logo="pack://application:,,,/images/Blocks/RedstoneBlock.png" />
-            </StackPanel>
-        </local:MyCard>
-        <local:MyCard Margin="0,20,0,15">
-            <Grid Margin="15,12">
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="1*"/>
-                    <ColumnDefinition Width="Auto" />
-                    <ColumnDefinition Width="Auto" />
-                    <ColumnDefinition Width="Auto" />
-                    <ColumnDefinition Width="1*"/>
-                </Grid.ColumnDefinitions>
-                <TextBlock Grid.Column="1" Text="联机服务由 " Opacity="0.5"/>
-                <local:MyTextButton Grid.Column="2" Text="阿猫" x:Name="BtnNoin" Opacity="0.5"
-                                    EventType="打开网页" EventData="https://mcer.cn" />
-                <TextBlock Grid.Column="3" Text=" 授权提供" Opacity="0.5"/>
-            </Grid>
-        </local:MyCard>
-    </StackPanel>
+                </StackPanel>
+            </local:MyCard>
+        </StackPanel>
+    </local:MyScrollViewer>
 </local:MyPageRight>

+ 0 - 1
Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml.vb

@@ -1,3 +1,2 @@
 Public Class PageLinkFeedback
-
 End Class

Разница между файлами не показана из-за своего большого размера
+ 108 - 0
Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml


+ 4 - 2
Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml

@@ -8,7 +8,6 @@
     <Grid>
         <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
             <StackPanel Orientation="Vertical" Margin="25,10">
-                <local:MyHint Margin="0,15,0,0" Text="联机功能仍处于测试阶段,尚未制作完成,因此延迟很高,且存在较多 Bug。延迟与内存占用高的问题将在后续优化,请多多包涵……" />
                 <local:MyCard Margin="0,15,0,0" x:Name="CardUser" Title="已连接的玩家 (0)">
                     <StackPanel Margin="20,37,20,15">
                         <local:MyHint Margin="5,2,5,8" x:Name="LabHint" IsWarn="False" />
@@ -19,7 +18,7 @@
                                 <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                             </Grid.ColumnDefinitions>
                             <local:MyButton x:Name="BtnLeftCreate" Grid.Column="0" MinWidth="140" Text="建立连接" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                            <local:MyButton x:Name="BtnLeftCopy" Grid.Column="1" MinWidth="140" Text="复制连接码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
+                            <local:MyButton x:Name="BtnLeftCopy" Grid.Column="1" MinWidth="140" Text="复制联机码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
                         </Grid>
                     </StackPanel>
                 </local:MyCard>
@@ -44,5 +43,8 @@
         <local:MyCard Opacity="0" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40,0" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False">
             <local:MyLoading Text="正在加载联机模块" Margin="20,20,20,17" x:Name="Load" ShowProgress="True" HorizontalAlignment="Center" VerticalAlignment="Center" />
         </local:MyCard>
+        <Grid x:Name="PanAlways" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="15" Opacity="0.5">
+            <local:MyTextButton Text="联机服务由 速聚 授权提供" EventType="打开网页" EventData="https://www.the.bb/zh-cn" />
+        </Grid>
     </Grid>
 </local:MyPageRight>

+ 8 - 18
Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml

@@ -6,28 +6,18 @@
       xmlns:local="clr-namespace:PCL"
       mc:Ignorable="d" AnimatedControl="{Binding ElementName=PanItem}" MinWidth="150">
     <StackPanel Orientation="Vertical" x:Name="PanItem" Margin="0,12,0,0">
-        <local:MyListItem x:Name="ItemNet" IsScaleAnimationEnabled="False" Type="None" PaddingLeft="2" Height="36" Title="网络检测中" PaddingRight="35" 
-            LogoScale="0.75" Logo="M512 817c-48.601 0-88-39.399-88-88s39.399-88 88-88 88 39.399 88 88-39.399 88-88 88zM237.671 565.74C308.335 474.58 397.369 429 504.774 429c118.433 0 214.225 55.421 287.377 166.264l-53.407 30.369c-13.84 7.87-31.362 4.367-41.114-8.219-50.291-64.911-114.577-97.367-192.856-97.367-86.851 0-156.835 40.318-209.95 120.953l-47.995-28.02c-15.263-8.91-20.412-28.507-11.502-43.77a32 32 0 0 1 2.344-3.47zM107.691 419.47C205.24 278.491 337.805 208 505.379 208c178.77 0 317.694 80.224 416.772 240.672l-56.54 31.73c-13.686 7.68-30.922 4.303-40.697-7.975C735.581 360.213 629.07 303.009 505.38 300.815 373.997 298.485 261.637 362.88 168.3 494l-50.116-28.505c-15.362-8.738-20.732-28.275-11.994-43.637a32 32 0 0 1 1.5-2.387z"
-            ToolTipService.Placement="Bottom" ToolTipService.HorizontalOffset="10" ToolTipService.VerticalOffset="0" ToolTipService.ShowDuration="2333333" ToolTipService.InitialShowDelay="20">
-            <local:MyListItem.Buttons>
-                <x:Array Type="{x:Type local:MyIconButton}">
-                    <local:MyIconButton Click="BtnNetRescan_Click" ToolTip="重新检测" ToolTipService.Placement="Right" ToolTipService.HorizontalOffset="5" ToolTipService.VerticalOffset="-2" ToolTipService.ShowDuration="2333333" ToolTipService.InitialShowDelay="200" Visibility="Collapsed"
-                                    LogoScale="0.85" Logo="M875.52 148.48C783.36 56.32 655.36 0 512 0 291.84 0 107.52 138.24 30.72 332.8l122.88 46.08C204.8 230.4 348.16 128 512 128c107.52 0 199.68 40.96 271.36 112.64L640 384h384V0L875.52 148.48zM512 896c-107.52 0-199.68-40.96-271.36-112.64L384 640H0v384l148.48-148.48C240.64 967.68 368.64 1024 512 1024c220.16 0 404.48-138.24 481.28-332.8L870.4 645.12C819.2 793.6 675.84 896 512 896z" />
-                </x:Array>
-            </local:MyListItem.Buttons>
-        </local:MyListItem>
-        <TextBlock Text="联机方式" Margin="13,18,5,5" Opacity="0.6" FontSize="12" />
-        <local:MyListItem x:Name="ItemCato" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="2" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="Cato" Visibility="Collapsed" 
+        <TextBlock Text="联机方式" Margin="13,6,5,4" Opacity="0.6" FontSize="12" />
+        <local:MyListItem x:Name="ItemHiper" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="1" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="HiPer"
                           LogoScale="0.85" Logo="M554.496 170.496c141.312 0 256 114.688 256 256s-114.688 256-256 256H402.432c-22.016-1.536-39.424-19.968-39.424-42.496a42.391 42.391 0 0 1 42.496-42.496h149.504c94.208 0 170.496-76.288 170.496-170.496S648.704 256 554.496 256h-256C204.288 256 128 332.288 128 426.496c0 38.4 12.8 73.728 33.792 102.4 5.632 7.168 8.704 15.872 8.704 25.6A42.391 42.391 0 0 1 128 596.992c-11.264 0-20.992-4.096-28.672-11.264l-0.512 0.512c-35.328-44.032-56.32-99.328-56.32-159.744 0-141.312 114.688-256 256-256h256z m64 192h3.072c22.016 1.536 39.424 19.968 39.424 42.496a42.391 42.391 0 0 1-42.496 42.496H469.504c-94.208 0-170.496 76.288-170.496 170.496S375.296 788.48 469.504 788.48h256C819.712 788.48 896 712.192 896 617.984c0-38.4-12.8-73.728-33.792-102.4-5.632-7.168-8.704-15.872-8.704-25.6A42.391 42.391 0 0 1 896 447.488c11.264 0 20.992 4.096 28.672 11.264l0.512-0.512c35.328 44.032 56.32 99.328 56.32 159.744 0 141.312-114.688 256-256 256h-256c-141.312 0-256-114.688-256-256s114.688-256 256-256h148.992z">
             <local:MyListItem.Buttons>
                 <x:Array Type="{x:Type local:MyIconButton}">
-                    <local:MyIconButton ToolTip="关闭联机模块" Loaded="BtnCatoStop_Loaded" Click="BtnCatoStop_Click"
+                    <local:MyIconButton ToolTip="关闭联机模块" Loaded="BtnHiperStop_Loaded" Click="BtnHiperStop_Click"
                                         ToolTipService.Placement="Right" ToolTipService.HorizontalOffset="5" ToolTipService.VerticalOffset="-2" ToolTipService.ShowDuration="2333333" ToolTipService.InitialShowDelay="200" LogoScale="0.7" 
                                         Logo="M576 512l277.333333 277.333333-64 64-277.333333-277.333333L234.666667 853.333333 170.666667 789.333333l277.333333-277.333333L170.666667 234.666667 234.666667 170.666667l277.333333 277.333333L789.333333 170.666667 853.333333 234.666667 576 512z" />
                 </x:Array>
             </local:MyListItem.Buttons>
         </local:MyListItem>
-        <local:MyListItem x:Name="ItemIoi" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="3" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="IOI"
+        <local:MyListItem x:Name="ItemIoi" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="2" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="IOI"
                           LogoScale="0.85" Logo="M554.496 170.496c141.312 0 256 114.688 256 256s-114.688 256-256 256H402.432c-22.016-1.536-39.424-19.968-39.424-42.496a42.391 42.391 0 0 1 42.496-42.496h149.504c94.208 0 170.496-76.288 170.496-170.496S648.704 256 554.496 256h-256C204.288 256 128 332.288 128 426.496c0 38.4 12.8 73.728 33.792 102.4 5.632 7.168 8.704 15.872 8.704 25.6A42.391 42.391 0 0 1 128 596.992c-11.264 0-20.992-4.096-28.672-11.264l-0.512 0.512c-35.328-44.032-56.32-99.328-56.32-159.744 0-141.312 114.688-256 256-256h256z m64 192h3.072c22.016 1.536 39.424 19.968 39.424 42.496a42.391 42.391 0 0 1-42.496 42.496H469.504c-94.208 0-170.496 76.288-170.496 170.496S375.296 788.48 469.504 788.48h256C819.712 788.48 896 712.192 896 617.984c0-38.4-12.8-73.728-33.792-102.4-5.632-7.168-8.704-15.872-8.704-25.6A42.391 42.391 0 0 1 896 447.488c11.264 0 20.992 4.096 28.672 11.264l0.512-0.512c35.328 44.032 56.32 99.328 56.32 159.744 0 141.312-114.688 256-256 256h-256c-141.312 0-256-114.688-256-256s114.688-256 256-256h148.992z">
             <local:MyListItem.Buttons>
                 <x:Array Type="{x:Type local:MyIconButton}">
@@ -37,8 +27,8 @@
                 </x:Array>
             </local:MyListItem.Buttons>
         </local:MyListItem>
-        <TextBlock Text="更多选项" Margin="13,18,5,5" Opacity="0.6" FontSize="12" />
-        <local:MyListItem x:Name="ItemSetup" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="5" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="设置"
+        <TextBlock Text="更多选项" Margin="13,18,5,4" Opacity="0.6" FontSize="12" />
+        <local:MyListItem x:Name="ItemSetup" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="4" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="设置"
                           LogoScale="0.85" Logo="M940.4 463.7L773.3 174.2c-17.3-30-49.2-48.4-83.8-48.4H340.2c-34.6 0-66.5 18.5-83.8 48.4L89.2 463.7c-17.3 30-17.3 66.9 0 96.8L256.4 850c17.3 30 49.2 48.4 83.8 48.4h349.2c34.6 0 66.5-18.5 83.8-48.4l167.2-289.5c17.3-29.9 17.3-66.8 0-96.8z m-94.6 96.8L725.9 768.1c-17.3 30-49.2 48.4-83.8 48.4H387.5c-34.6 0-66.5-18.5-83.8-48.4L183.9 560.5c-17.3-30-17.3-66.9 0-96.8l119.8-207.5c17.3-30 49.2-48.4 83.8-48.4h254.6c34.6 0 66.5 18.5 83.8 48.4l119.8 207.5c17.3 30 17.3 66.9 0.1 96.8z M522.3 321.2c-2.5-0.1-5-0.2-7.5-0.2-119.9 0-214 110.3-186.3 235 15.8 70.9 71.5 126.6 142.4 142.4 17.5 3.9 34.7 5.4 51.4 4.7 102.1-3.9 183.6-87.9 183.6-191 0.1-103-81.5-187-183.6-190.9z m68.6 269.1c-18.5 18-43 28.9-68.6 30.7l-6 0.3c-30.2 0.4-58.6-11.4-79.7-33-19.5-20.1-30.7-47-30.9-75-0.3-29.6 11.1-57.4 32-78.3 20.6-20.6 48-32 77.2-32 2.5 0 5 0.1 7.5 0.3 26.7 1.8 51.5 13.2 70.5 32.5 19.6 20 30.8 46.9 31.2 74.9 0.2 30.2-11.5 58.6-33.2 79.6z">
             <local:MyListItem.Buttons>
                 <x:Array Type="{x:Type local:MyIconButton}">
@@ -46,9 +36,9 @@
                 </x:Array>
             </local:MyListItem.Buttons>
         </local:MyListItem>
-        <local:MyListItem x:Name="ItemHelp" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="6" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="常见问题"
+        <local:MyListItem x:Name="ItemHelp" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="5" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="常见问题"
                           LogoScale="0.97" Logo="M520.6 620.3c-11.3-0.6-20.1-4-26.9-10.5-6.6-6.3-8.1-11-10.1-20.8-1.9-9.5-1.5-16.7-1-24.9l0.3-5.3c0.5-9.9 3.5-19.6 8.8-28.1 5.7-9 12.5-17.2 20.8-25.1 8.6-8 17.7-15.7 27-22.9 9.4-7.2 18.7-14.8 27.7-22.7 9-7.9 16.2-15.8 22.1-24.3 6.2-9 9.3-18.8 9.3-29.4 1.2-20.3-6.5-37.7-22.8-51.3-16-13.3-37.4-20-63.5-20-13.7 0-26.3 3.3-37.4 10-10.9 6.5-20.2 14.5-27.7 23.8-7.5 9.2-13.5 19-17.9 29.2-4.5 10.6-6.7 19.4-6.7 26.8 0 9.2-3.3 16-10.1 20.8-6.9 4.8-14.6 7.3-22.8 7.3h-1.3c-9-0.3-17-3.3-24.6-9.2-7.3-5.6-11.1-14.6-11.6-27.5-0.6-11.5 1.9-26.1 7.2-43.5 5.4-17.4 14.8-34.4 28.1-50.5 13.4-16.3 31.2-30.4 52.8-41.8 21.6-11.4 49.2-17.2 81.9-17.2 26.4 0 49.6 3.9 69.1 11.7 19.4 7.7 35.4 18.2 47.5 31.1l1.6 1.7c11.8 12.6 20.3 21.7 29.4 44.4 11.6 28.9 7.1 52.1 5.5 58.5-5.8 22.6-12.6 37.7-22.7 50.4-11.7 14.6-24.9 28.2-39.2 40.4-14 11.7-39.2 33.3-39.2 33.3-13.3 11.4-19.4 22.1-18.6 32.9V587c0.5 8.7-2.4 16.1-8.8 22.8-6.2 6.4-14.8 10-26.2 10.5zM519 766.1c-13 0-23.6-4.2-32.2-12.9-8.7-8.7-13-19-13-31.4 0-12.9 4.2-23.5 12.9-32.2 8.7-8.7 19.2-12.9 32.2-12.9 13 0 23.5 4.2 32.2 12.9s12.9 19.2 12.9 32.2c0 12.4-4.2 22.7-12.9 31.4-8.6 8.6-19.2 12.9-32.1 12.9z M515 928.3c-228.1 0-413.7-185.6-413.7-413.7S286.9 100.9 515 100.9s413.7 185.6 413.7 413.7c0.1 228.1-185.5 413.7-413.7 413.7z m0-747.4c-184 0-333.7 149.7-333.7 333.7S331 848.3 515 848.3s333.7-149.7 333.7-333.7S699 180.9 515 180.9z" />
-        <local:MyListItem x:Name="ItemFeedback" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="7" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="反馈"
+        <local:MyListItem x:Name="ItemFeedback" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="6" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="关于与反馈"
                           LogoScale="0.9" Logo="M613.717333 64.426667l3.413334 3.242666 331.861333 331.861334a85.333333 85.333333 0 0 1 3.2 117.269333l-3.2 3.413333L573.162667 896H960a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333H64a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h295.338667L74.069333 610.709333a85.333333 85.333333 0 0 1-3.221333-117.269333l3.2-3.413333 422.4-422.357334a85.333333 85.333333 0 0 1 117.269333-3.221333z m-20.629333 99.882666l-21.418667 321.493334a85.333333 85.333333 0 0 1-74.944 79.04l-4.522666 0.426666-321.493334 21.44 295.552 295.530667 422.4-422.378667L593.066667 164.309333z m-86.442667 13.824L184.533333 500.266667l301.994667-20.117334 20.117333-301.994666z" />
     </StackPanel>
 </local:MyPageLeft>

+ 192 - 0
Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml.vb

@@ -0,0 +1,192 @@
+Imports NetFwTypeLib
+
+Public Class PageLinkLeft
+
+    Private IsLoad As Boolean = False
+    Private IsPageSwitched As Boolean = False '如果在 Loaded 前切换到其他页面,会导致触发 Loaded 时再次切换一次
+    Private Sub PageLinkLeft_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
+        If IsLoad Then Exit Sub
+        IsLoad = True
+        '切换默认页面
+        If IsPageSwitched Then Exit Sub
+        ItemHiper.SetChecked(True, False, False)
+    End Sub
+    Private Sub PageOtherLeft_Unloaded(sender As Object, e As RoutedEventArgs) Handles Me.Unloaded
+        IsPageSwitched = False
+    End Sub
+
+#Region "Windows 防火墙"
+
+    '为联机模块添加防火墙通行权限的函数库
+    '不加防火墙权限你要我怎么办,这联机根本连都连不上,球球你们别看到防火墙就说是后门了,真的很恼火……
+
+    ''' <summary>
+    ''' 获取某个文件是否被 Windows 防火墙阻止。
+    ''' </summary>
+    Public Shared Function FirewallIsBlock(FilePath As String) As Boolean
+        '获取配置
+        If FirewallPolicy Is Nothing Then
+            Try
+                FirewallPolicy = FirewallPolicyGet()
+            Catch ex As Exception
+                Log(ex, "Windows 防火墙:可能关闭得太死了,根本检测不到,笑死", LogLevel.Normal)
+                Return False
+            End Try
+        End If
+        '检查是否开启
+        If Not FirewallPolicy.CurrentProfile.FirewallEnabled Then
+            Log("[Link] Windows 防火墙:防火墙关闭")
+            Return False
+        End If
+        '检查是否允许例外
+        If FirewallPolicy.CurrentProfile.ExceptionsNotAllowed Then
+            Log("[Link] Windows 防火墙:防火墙开启,不允许例外")
+            Return True
+        End If
+        '检查白名单
+        Dim Target As String = FilePath.Replace(PathTemp.Split("\").First, "").ToLower
+        For Each App As INetFwAuthorizedApplication In FirewallPolicy.CurrentProfile.AuthorizedApplications
+            If ModeDebug Then Log("[Link] 防火墙白名单(" & App.Enabled & "):" & App.ProcessImageFileName)
+            If Not App.Enabled Then Continue For
+            '信标的奇妙盘符 E:0/xxxxx/xxx 导致误判……
+            Dim FileNameTrimmed As String = App.ProcessImageFileName.Replace(App.ProcessImageFileName.Split("\").First, "")
+            If App.ProcessImageFileName.ToLower.Contains(FilePath) Then
+                Log("[Link] Windows 防火墙:开启,已在白名单中(" & FilePath & ")")
+                Return False
+            End If
+        Next
+        Log("[Link] Windows 防火墙:开启,未在白名单中(" & FilePath & ")")
+        Return True
+    End Function
+    ''' <summary>
+    ''' 添加防火墙通行权限。需要管理员权限。
+    ''' </summary>
+    Public Shared Sub FirewallAddAuthorized(DisplayName As String, FilePath As String)
+        Log("[Link] Windows 防火墙:添加通行权限(" & FilePath & ")")
+        '获取配置
+        If FirewallPolicy Is Nothing Then FirewallPolicy = FirewallPolicyGet()
+        '添加项目
+        FirewallPolicy.GetProfileByType(NET_FW_PROFILE_TYPE_.NET_FW_PROFILE_DOMAIN).AuthorizedApplications.Add(New FirewallApp(DisplayName, FilePath))
+        FirewallPolicy.GetProfileByType(NET_FW_PROFILE_TYPE_.NET_FW_PROFILE_STANDARD).AuthorizedApplications.Add(New FirewallApp(DisplayName, FilePath))
+        FirewallPolicy.CurrentProfile.AuthorizedApplications.Add(New FirewallApp(DisplayName, FilePath))
+    End Sub
+    Private Class FirewallApp
+        Implements INetFwAuthorizedApplication
+        Public Property Name As String Implements INetFwAuthorizedApplication.Name
+        Public Property ProcessImageFileName As String Implements INetFwAuthorizedApplication.ProcessImageFileName
+        Public Property IpVersion As NET_FW_IP_VERSION_ = NET_FW_IP_VERSION_.NET_FW_IP_VERSION_ANY Implements INetFwAuthorizedApplication.IpVersion
+        Public Property Scope As NET_FW_SCOPE_ = NET_FW_SCOPE_.NET_FW_SCOPE_ALL Implements INetFwAuthorizedApplication.Scope
+        Public Property RemoteAddresses As String = "*" Implements INetFwAuthorizedApplication.RemoteAddresses
+        Public Property Enabled As Boolean = True Implements INetFwAuthorizedApplication.Enabled
+        Public Sub New(DisplayName As String, FilePath As String)
+            Name = DisplayName
+            ProcessImageFileName = FilePath
+        End Sub
+    End Class
+    '获取防火墙配置
+    Public Shared FirewallPolicy As INetFwPolicy = Nothing
+    Public Shared Function FirewallPolicyGet() As INetFwPolicy
+        Const CLSID_FIREWALL_MANAGER As String = "{304CE942-6E39-40D8-943A-B913C40C9CD4}"
+        Dim objType As Type = Type.GetTypeFromCLSID(New Guid(CLSID_FIREWALL_MANAGER))
+        Return TryCast(Activator.CreateInstance(objType), INetFwMgr).LocalPolicy
+    End Function
+
+#End Region
+
+#Region "页面切换"
+
+    ''' <summary>
+    ''' 当前页面的编号。
+    ''' </summary>
+    Public PageID As FormMain.PageSubType = FormMain.PageSubType.LinkHiper
+
+    ''' <summary>
+    ''' 勾选事件改变页面。
+    ''' </summary>
+    Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemHiper.Check, ItemIoi.Check, ItemSetup.Check, ItemHelp.Check, ItemFeedback.Check
+        '尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0
+        '若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False)
+        If sender.Tag IsNot Nothing Then PageChange(Val(sender.Tag))
+    End Sub
+
+    Public Function PageGet(Optional ID As FormMain.PageSubType = -1)
+        If ID = -1 Then ID = PageID
+        Select Case ID
+            Case 0, FormMain.PageSubType.LinkHiper
+                If FrmLinkHiper Is Nothing Then FrmLinkHiper = New PageLinkHiper
+                Return FrmLinkHiper
+            Case FormMain.PageSubType.LinkIoi
+                If FrmLinkIoi Is Nothing Then FrmLinkIoi = New PageLinkIoi
+                Return FrmLinkIoi
+            Case FormMain.PageSubType.LinkSetup
+                If FrmSetupLink Is Nothing Then FrmSetupLink = New PageSetupLink
+                Return FrmSetupLink
+            Case FormMain.PageSubType.LinkHelp
+                If FrmLinkHelp Is Nothing Then FrmLinkHelp = PageOtherHelp.GetHelpPage(PathTemp & "Help\启动器\联机.json")
+                Return FrmLinkHelp
+            Case FormMain.PageSubType.LinkFeedback
+                If FrmLinkFeedback Is Nothing Then FrmLinkFeedback = New PageLinkFeedback
+                Return FrmLinkFeedback
+            Case Else
+                Throw New Exception("未知的更多子页面种类:" & ID)
+        End Select
+    End Function
+
+    ''' <summary>
+    ''' 切换现有页面。
+    ''' </summary>
+    Public Sub PageChange(ID As FormMain.PageSubType)
+        If PageID = ID Then Exit Sub
+        AniControlEnabled += 1
+        IsPageSwitched = True
+        Try
+            PageChangeRun(PageGet(ID))
+            PageID = ID
+        Catch ex As Exception
+            Log(ex, "切换设置分页面失败(ID " & ID & ")", LogLevel.Feedback)
+        Finally
+            AniControlEnabled -= 1
+        End Try
+    End Sub
+    Private Shared Sub PageChangeRun(Target As MyPageRight)
+        AniStop("FrmMain PageChangeRight") '停止主页面的右页面切换动画,防止它与本动画一起触发多次 PageOnEnter
+        If Target.Parent IsNot Nothing Then Target.SetValue(ContentPresenter.ContentProperty, Nothing)
+        FrmMain.PageRight = Target
+        CType(FrmMain.PanMainRight.Child, MyPageRight).PageOnExit()
+        AniStart({
+                         AaCode(Sub()
+                                    CType(FrmMain.PanMainRight.Child, MyPageRight).PageOnForceExit()
+                                    FrmMain.PanMainRight.Child = FrmMain.PageRight
+                                    FrmMain.PageRight.Opacity = 0
+                                End Sub, 130),
+                         AaCode(Sub()
+                                    '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
+                                    FrmMain.PageRight.Opacity = 1
+                                    FrmMain.PageRight.PageOnEnter()
+                                End Sub, 30, True)
+                     }, "PageLeft PageChange")
+    End Sub
+
+#End Region
+
+    Public Sub Reset(sender As Object, e As EventArgs)
+        If MyMsgBox("是否要初始化联机页的所有设置?该操作不可撤销。", "初始化确认",, "取消", IsWarn:=True) = 1 Then
+            If IsNothing(FrmSetupLink) Then FrmSetupLink = New PageSetupLink
+            FrmSetupLink.Reset()
+        End If
+    End Sub
+
+    Private Sub BtnHiperStop_Loaded(sender As Object, e As RoutedEventArgs)
+        sender.Visibility = If(PageLinkHiper.HiperState = LoadState.Finished OrElse PageLinkHiper.HiperState = LoadState.Loading, Visibility.Visible, Visibility.Collapsed)
+    End Sub
+    Private Sub BtnHiperStop_Click(sender As Object, e As EventArgs)
+        PageLinkHiper.ModuleStopManually()
+    End Sub
+    Private Sub BtnIoiStop_Loaded(sender As Object, e As RoutedEventArgs)
+        sender.Visibility = If(PageLinkIoi.InitLoader.State = LoadState.Finished, Visibility.Visible, Visibility.Collapsed)
+    End Sub
+    Private Sub BtnIoiStop_Click(sender As Object, e As EventArgs)
+        PageLinkIoi.ModuleStopManually()
+    End Sub
+
+End Class

+ 1 - 1
Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb

@@ -238,7 +238,7 @@
                 For Each VersionPath In VersionFolder.EnumerateDirectories
                     Dim Version As New McVersion(VersionPath.FullName)
                     Version.Load()
-                    If Not Version.Version.Modable Then Continue For
+                    If Not Version.Modable Then Continue For
                     '4. 该版本的隔离文件夹下不存在 mods
                     Dim ModIndieFolder As New DirectoryInfo(Version.Path & "mods\")
                     If ModIndieFolder.Exists AndAlso ModIndieFolder.EnumerateFiles.Count > 0 Then Exit Sub

+ 7 - 7
Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb

@@ -114,8 +114,8 @@
         '设置文件
         Try
             '拷贝文件
-            File.Delete(PathTemp & "CustomSkin.png")
-            CopyFile(SkinInfo.LocalFile, PathTemp & "CustomSkin.png")
+            File.Delete(PathAppdata & "CustomSkin.png")
+            CopyFile(SkinInfo.LocalFile, PathAppdata & "CustomSkin.png")
             '更新设置
             Setup.Set("LaunchSkinSlim", SkinInfo.IsSlim)
         Catch ex As Exception
@@ -129,7 +129,7 @@
         If Not (AniControlEnabled = 0 AndAlso e.RaiseByMouse) Then Exit Sub
         Try
             '已有图片则不再选择
-            If File.Exists(PathTemp & "CustomSkin.png") Then Exit Sub
+            If File.Exists(PathAppdata & "CustomSkin.png") Then Exit Sub
             '没有图片则要求选择
             Dim SkinInfo As McSkinInfo = McSkinSelect(True)
             If Not SkinInfo.IsVaild Then
@@ -137,8 +137,8 @@
                 Exit Sub
             End If
             '拷贝文件
-            File.Delete(PathTemp & "CustomSkin.png")
-            CopyFile(SkinInfo.LocalFile, PathTemp & "CustomSkin.png")
+            File.Delete(PathAppdata & "CustomSkin.png")
+            CopyFile(SkinInfo.LocalFile, PathAppdata & "CustomSkin.png")
             '更新设置
             Setup.Set("LaunchSkinSlim", SkinInfo.IsSlim)
         Catch ex As Exception
@@ -151,7 +151,7 @@
     End Sub
     Private Sub BtnSkinDelete_Click(sender As Object, e As EventArgs) Handles BtnSkinDelete.Click
         Try
-            File.Delete(PathTemp & "CustomSkin.png")
+            File.Delete(PathAppdata & "CustomSkin.png")
             RadioSkinType0.SetChecked(True, True, True)
             Hint("离线皮肤已清空!", HintType.Finish)
         Catch ex As Exception
@@ -335,7 +335,7 @@
             Dim RamTarget2 As Double '估计没啥问题了的内存
             Dim RamTarget3 As Double '放一百万个材质和 Mod 和光影需要的内存
             If Version IsNot Nothing AndAlso Not Version.IsLoaded Then Version.Load()
-            If Version IsNot Nothing AndAlso Version.Version.Modable Then
+            If Version IsNot Nothing AndAlso Version.Modable Then
                 '可安装 Mod 的版本
                 Dim ModDir As New DirectoryInfo(Version.PathIndie & "mods\")
                 Dim ModCount As Integer = If(ModDir.Exists, ModDir.GetFiles.Length, 0)

+ 8 - 4
Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml

@@ -6,8 +6,14 @@
              d:DesignWidth="800" d:DesignHeight="800">
     <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
         <StackPanel x:Name="PanMain" Margin="25,10">
-            <local:MyCard Margin="0,20,0,-5" Title="通用设置">
-                <StackPanel Margin="25,40,25,15">
+            <local:MyCard Margin="0,15" Title="HiPer">
+                <StackPanel Margin="25,40,25,20" Orientation="Vertical">
+                    <local:MyCheckBox Text="在索引码还有 5 分钟到期时提醒我" x:Name="CheckHiperCertWarn" Tag="LinkHiperCertWarn" Height="22" />
+                    <local:MyButton x:Name="BtnHiperLog" Height="35" MinWidth="140" Text="查看日志" Padding="13,0" Margin="0,10,20,0" HorizontalAlignment="Left" />
+                </StackPanel>
+            </local:MyCard>
+            <local:MyCard Margin="0,0,0,15" Title="IOI">
+                <StackPanel Margin="25,40,25,20">
                     <Grid>
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
@@ -26,8 +32,6 @@
                             </local:MyTextBox.ValidateRules>
                         </local:MyTextBox>
                     </Grid>
-                    <local:MyCheckBox Text="打开启动器时启动联机模块" Height="22" x:Name="CheckLinkAuto" Tag="LinkAuto" ToolTipService.ShowDuration="2333333" Margin="0,10,0,4"
-                                          ToolTip="开启联机模块的人越多,所有使用 PCL2 的玩家的初次连接速度就会越快。因此,启用此项可以为大家作出贡献。&#13;联机模块仅使用少许网络带宽,几乎不使用硬盘与 CPU。" />
                 </StackPanel>
             </local:MyCard>
         </StackPanel>

+ 13 - 5
Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb

@@ -17,15 +17,15 @@
 
     End Sub
     Public Sub Reload()
-        CheckLinkAuto.Checked = Setup.Get("LinkAuto")
         TextLinkName.Text = Setup.Get("LinkName")
+        CheckHiperCertWarn.Checked = Setup.Get("LinkHiperCertWarn")
     End Sub
 
     '初始化
     Public Sub Reset()
         Try
-            Setup.Reset("LinkAuto")
             Setup.Reset("LinkName")
+            Setup.Reset("LinkHiperCertWarn")
 
             Log("[Setup] 已初始化联机页设置")
             Hint("已初始化联机页设置!", HintType.Finish, False)
@@ -37,11 +37,19 @@
     End Sub
 
     '将控件改变路由到设置改变
-    Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckLinkAuto.Change
-        If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked)
-    End Sub
     Private Shared Sub TextBoxChange(sender As MyTextBox, e As Object) Handles TextLinkName.ValidatedTextChanged
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Text)
     End Sub
+    Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckHiperCertWarn.Change
+        If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked)
+    End Sub
+
+    Private Sub BtnHiperLog_Click(sender As Object, e As EventArgs) Handles BtnHiperLog.Click
+        If File.Exists(PageLinkHiper.PathHiper & "logs\hiper.log") Then
+            OpenExplorer("/select,""" & PageLinkHiper.PathHiper & "logs\hiper.log""")
+        Else
+            Hint("没有找到 HiPer 联机模块的日志!")
+        End If
+    End Sub
 
 End Class

+ 0 - 1
Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb

@@ -183,7 +183,6 @@
         Dim Temp As String()
         Temp = Directory.GetFiles(Path)
         For Each str As String In Temp
-            If str = PathTemp & "CustomSkin.png" OrElse str = PathTemp & "联机模块.exe" Then Continue For '不删除的文件
             Try
                 Dim Info As New FileInfo(str)
                 Dim FileActualSize = Math.Ceiling(Info.Length / 4096) * 4096

+ 2 - 2
Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb

@@ -5,8 +5,8 @@
     ''' </summary>
     Public Shared Version As McVersion
 
-    Private Sub PageVersionLeft_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
-        If Version IsNot Nothing AndAlso Version.Version.Modable Then
+    Public Sub RefreshModDisabled() Handles Me.Loaded
+        If Version IsNot Nothing AndAlso Version.Modable Then
             ItemMod.Visibility = Visibility.Visible
             ItemModDisabled.Visibility = Visibility.Collapsed
         Else

+ 4 - 0
Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb

@@ -57,7 +57,11 @@
         If ComboDisplayType.SelectedIndex <> 1 Then
             '改为不隐藏
             Try
+                '若设置分类为可安装 Mod,则显示正常的 Mod 管理页面
                 WriteIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "DisplayType", ComboDisplayType.SelectedIndex)
+                PageVersionLeft.Version.DisplayType = ReadIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "DisplayType", McVersionCardType.Auto)
+                FrmVersionLeft.RefreshModDisabled()
+
                 WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '要求刷新缓存
                 LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
             Catch ex As Exception

+ 1 - 1
Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb

@@ -275,7 +275,7 @@
             Dim RamTarget2 As Double '估计没啥问题了的内存
             Dim RamTarget3 As Double '安装过多附加组件需要的内存
             If Version IsNot Nothing AndAlso Not Version.IsLoaded Then Version.Load()
-            If Version IsNot Nothing AndAlso Version.Version.Modable Then
+            If Version IsNot Nothing AndAlso Version.Modable Then
                 '可安装 Mod 的版本
                 Dim ModDir As New DirectoryInfo(Version.PathIndie & "mods\")
                 Dim ModCount As Integer = If(ModDir.Exists, ModDir.GetFiles.Length, 0)

+ 11 - 12
Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj

@@ -152,11 +152,6 @@
       <HintPath>Resources\Ookii.Dialogs.Wpf.dll</HintPath>
       <Private>False</Private>
     </Reference>
-    <Reference Include="STUN, Version=0.5.0.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>Resources\STUN.dll</HintPath>
-      <Private>False</Private>
-    </Reference>
     <Reference Include="System.Core" />
     <Reference Include="System.Drawing" />
     <Reference Include="System.IO.Compression" />
@@ -260,6 +255,9 @@
     <Compile Include="Pages\PageLink\PageLinkFeedback.xaml.vb">
       <DependentUpon>PageLinkFeedback.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Pages\PageLink\PageLinkHiper.xaml.vb">
+      <DependentUpon>PageLinkHiper.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Pages\PageOther\PageOtherHelpDetail.xaml.vb">
       <DependentUpon>PageOtherHelpDetail.xaml</DependentUpon>
     </Compile>
@@ -517,6 +515,10 @@
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </Page>
+    <Page Include="Pages\PageLink\PageLinkHiper.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="Pages\PageOther\PageOtherHelpDetail.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
@@ -786,22 +788,22 @@
   <ItemGroup>
     <PublishFile Include="NAudio">
       <Visible>False</Visible>
-      <PublishState>Exclude</PublishState>
-      <IncludeHash>True</IncludeHash>
       <Group>
       </Group>
       <TargetPath>
       </TargetPath>
+      <PublishState>Exclude</PublishState>
+      <IncludeHash>True</IncludeHash>
       <FileType>Assembly</FileType>
     </PublishFile>
     <PublishFile Include="Newtonsoft.Json">
       <Visible>False</Visible>
-      <PublishState>Exclude</PublishState>
-      <IncludeHash>True</IncludeHash>
       <Group>
       </Group>
       <TargetPath>
       </TargetPath>
+      <PublishState>Exclude</PublishState>
+      <IncludeHash>True</IncludeHash>
       <FileType>Assembly</FileType>
     </PublishFile>
   </ItemGroup>
@@ -869,9 +871,6 @@
   <ItemGroup>
     <Resource Include="Images\Heads\noin.png" />
   </ItemGroup>
-  <ItemGroup>
-    <None Include="Resources\STUN.dll" />
-  </ItemGroup>
   <ItemGroup>
     <WCFMetadata Include="Connected Services\" />
   </ItemGroup>

BIN
Plain Craft Launcher 2/Resources/Help.zip


BIN
Plain Craft Launcher 2/Resources/STUN.dll


BIN
Plain Craft Launcher 2/Resources/java-wrapper.jar


Некоторые файлы не были показаны из-за большого количества измененных файлов