龙腾猫跃 1 year ago
parent
commit
2210da6fda
62 changed files with 1408 additions and 1968 deletions
  1. 21 9
      Plain Craft Launcher 2/Application.xaml.vb
  2. 15 0
      Plain Craft Launcher 2/Controls/Interfaces.vb
  3. 2 18
      Plain Craft Launcher 2/Controls/MyCard.vb
  4. 12 50
      Plain Craft Launcher 2/Controls/MyImage.vb
  5. 1 5
      Plain Craft Launcher 2/Controls/MyLoading.xaml.vb
  6. 10 27
      Plain Craft Launcher 2/Controls/MyPageLeft.vb
  7. 9 22
      Plain Craft Launcher 2/Controls/MyPageRight.vb
  8. 16 13
      Plain Craft Launcher 2/FormMain.xaml.vb
  9. 35 27
      Plain Craft Launcher 2/Modules/Base/ModBase.vb
  10. 5 4
      Plain Craft Launcher 2/Modules/Base/ModLoader.vb
  11. 333 209
      Plain Craft Launcher 2/Modules/Base/ModNet.vb
  12. 6 1
      Plain Craft Launcher 2/Modules/Base/ModValidate.vb
  13. 7 8
      Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
  14. 15 11
      Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
  15. 16 16
      Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
  16. 11 26
      Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
  17. 45 52
      Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
  18. 25 15
      Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
  19. 9 8
      Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
  20. 19 11
      Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
  21. 0 6
      Plain Craft Launcher 2/Modules/ModMain.vb
  22. 4 3
      Plain Craft Launcher 2/Modules/ModSecret.vb
  23. 2 2
      Plain Craft Launcher 2/My Project/AssemblyInfo.vb
  24. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml
  25. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb
  26. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml
  27. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml
  28. 7 7
      Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
  29. 0 31
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml
  30. 0 111
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb
  31. 0 28
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml
  32. 0 32
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml.vb
  33. 0 26
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml
  34. 0 61
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb
  35. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml
  36. 3 26
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml
  37. 1 88
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb
  38. 0 26
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml
  39. 0 52
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb
  40. 0 27
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml
  41. 0 34
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb
  42. 0 26
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml
  43. 0 53
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb
  44. 3 2
      Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb
  45. 21 21
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb
  46. 80 77
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb
  47. 13 14
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb
  48. 493 452
      Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml
  49. 2 2
      Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb
  50. 0 3
      Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb
  51. 2 2
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml
  52. 9 1
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb
  53. 1 1
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
  54. 1 1
      Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb
  55. 8 8
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml
  56. 2 1
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb
  57. 2 2
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
  58. 10 46
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb
  59. 3 2
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml
  60. 8 0
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb
  61. 1 42
      Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
  62. 115 115
      Plain Craft Launcher 2/Resources/ModData.txt

+ 21 - 9
Plain Craft Launcher 2/Application.xaml.vb

@@ -67,9 +67,9 @@ Public Class Application
                 If Not CheckPermission(PathTemp) Then Throw New Exception("PCL 没有对 " & PathTemp & " 的访问权限")
             Catch ex As Exception
                 If PathTemp = IO.Path.GetTempPath() & "PCL\" Then
-                    MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & "错误原因:" & GetExceptionDetail(ex), "缓存文件夹不可用")
+                    MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDetail(), "缓存文件夹不可用")
                 Else
-                    MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & "错误原因:" & GetExceptionDetail(ex), "缓存文件夹不可用")
+                    MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDetail(), "缓存文件夹不可用")
                     Setup.Set("SystemSystemCache", "")
                     PathTemp = IO.Path.GetTempPath() & "PCL\"
                 End If
@@ -104,6 +104,13 @@ WaitRetry:
             ToolTipService.PlacementProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(Primitives.PlacementMode.Bottom))
             ToolTipService.HorizontalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(8.0))
             ToolTipService.VerticalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(4.0))
+            '设置网络配置默认值
+            ServicePointManager.Expect100Continue = False
+            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
+            ServicePointManager.DefaultConnectionLimit = 10000
+            ServicePointManager.UseNagleAlgorithm = False
+            ServicePointManager.EnableDnsRoundRobin = True
+            ServicePointManager.ReusePort = True
             '设置初始窗口
             If Setup.Get("UiLauncherLogo") Then
                 FrmStart = New SplashScreen("Images\icon.ico")
@@ -136,11 +143,6 @@ WaitRetry:
             Setup.Load("ToolDownloadThread")
             Setup.Load("ToolDownloadCert")
             Setup.Load("ToolDownloadSpeed")
-            '网络配置初始化
-            ServicePointManager.Expect100Continue = True
-            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
-            ServicePointManager.DefaultConnectionLimit = 1024
-            ServicePointManager.UseNagleAlgorithm = False
             '计时
             Log("[Start] 第一阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
             ApplicationStartTick = GetTimeTick()
@@ -155,7 +157,7 @@ WaitRetry:
                 FilePath = PathWithName
             Catch
             End Try
-            MsgBox(GetExceptionDetail(ex, True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误")
+            MsgBox(ex.GetDetail(True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误")
             FormMain.EndProgramForce(ProcessReturnValues.Exception)
         End Try
     End Sub
@@ -168,10 +170,20 @@ WaitRetry:
     '异常
     Private Sub Application_DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) Handles Me.DispatcherUnhandledException
         On Error Resume Next
+        '触发页面的 Dispatcher
+        If FrmMain?.PageLeft IsNot Nothing AndAlso TypeOf FrmMain.PageLeft Is IDispatcherUnhandledException Then
+            CType(FrmMain.PageLeft, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e)
+            If e.Handled Then Return
+        End If
+        If FrmMain?.PageRight IsNot Nothing AndAlso TypeOf FrmMain.PageRight Is IDispatcherUnhandledException Then
+            CType(FrmMain.PageRight, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e)
+            If e.Handled Then Return
+        End If
+        '正常处理
         e.Handled = True
         If IsProgramEnded Then Return
         FeedbackInfo()
-        Dim Detail As String = GetExceptionDetail(e.Exception, True)
+        Dim Detail As String = e.Exception.GetDetail(True)
         If Detail.Contains("System.Windows.Threading.Dispatcher.Invoke") OrElse Detail.Contains("MS.Internal.AppModel.ITaskbarList.HrInit") OrElse Detail.Contains("未能加载文件或程序集") OrElse
            Detail.Contains(".NET Framework") Then ' “自动错误判断” 的结果分析
             OpenWebsite("https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net462-offline-installer")

+ 15 - 0
Plain Craft Launcher 2/Controls/Interfaces.vb

@@ -0,0 +1,15 @@
+Imports System.Windows.Threading
+
+''' <summary>
+''' 指示页面可以通过按下 F5 或以其他方式刷新。
+''' </summary>
+Public Interface IRefreshable
+    Sub Refresh()
+End Interface
+
+''' <summary>
+''' 指示页面可以处理未捕获的异常。
+''' </summary>
+Public Interface IDispatcherUnhandledException
+    Sub DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs)
+End Interface

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

@@ -123,14 +123,6 @@
         End Select
         '控件转换
         Select Case Type
-            Case 5
-                Dim LoadingPickaxe As New MyLoading With {.Text = "正在获取版本列表", .Margin = New Thickness(5)}
-                Dim Loader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion Main", AddressOf DlForgeVersionMain)
-                LoadingPickaxe.State = Loader
-                Loader.Start(Stack.Tag)
-                AddHandler LoadingPickaxe.StateChanged, AddressOf FrmDownloadForge.Forge_StateChanged
-                AddHandler LoadingPickaxe.Click, AddressOf FrmDownloadForge.Forge_Click
-                Stack.Children.Add(LoadingPickaxe)
             Case 6
                 ForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf ForgeSave_Click, True)
             Case 8
@@ -145,9 +137,6 @@
                     Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True))
                 Case 3
                     Stack.Children.Add(OptiFineDownloadListItem(Data, AddressOf OptiFineSave_Click, True))
-                Case 4
-                    Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf FrmDownloadLiteLoader.DownloadStart, False))
-                Case 5
                 Case 6
                     Stack.Children.Add(ForgeDownloadListItem(Data, AddressOf ForgeSave_Click, True))
                 Case 7
@@ -332,13 +321,8 @@
             SetValue(IsSwapedProperty, value)
         End Set
     End Property
-    Public Shared ReadOnly IsSwapedProperty As DependencyProperty = DependencyProperty.Register("IsSwaped", GetType(Boolean), GetType(MyCard), New PropertyMetadata(False, AddressOf OnIsSwapedPropertyChanged))
-    Private Shared Sub OnIsSwapedPropertyChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
-        Dim self = TryCast(d, MyCard)
-        If self IsNot Nothing Then
-            self.IsSwapped = CType(e.NewValue, Boolean)
-        End If
-    End Sub
+    Public Shared ReadOnly IsSwapedProperty As DependencyProperty = DependencyProperty.Register("IsSwaped", GetType(Boolean), GetType(MyCard), New PropertyMetadata(False,
+    Sub(sender, e) If sender IsNot Nothing AndAlso TypeOf sender Is MyCard Then CType(sender, MyCard).IsSwapped = CType(e.NewValue, Boolean)))
 
     Public Property SwapLogoRight As Boolean = False
     Private IsMouseDown As Boolean = False

+ 12 - 50
Plain Craft Launcher 2/Controls/MyImage.vb

@@ -25,7 +25,7 @@
 
     ''' <summary>
     ''' 与 Image 的 Source 类似。
-    ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示,图片会保存为本地缓存。
+    ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示;若 EnableCache 设为 True,图片还会保存为本地缓存。
     ''' 支持 WebP 格式的图片。
     ''' </summary>
     Public Shadows Property Source As String '覆写 Image 的 Source 属性
@@ -46,7 +46,7 @@
     Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString())))
 
     ''' <summary>
-    ''' 当 Source 首次下载失败时,会从该备用地址加载图片。
+    ''' 若 Source 是一个网络图片,该地址将作为第二下载源。
     ''' </summary>
     Public Property FallbackSource As String
         Get
@@ -111,74 +111,36 @@
             Return
         End If
         '从缓存加载网络图片
-        Dim Url As String = Source
-        Dim Retried As Boolean = False
-        Dim TempPath As String = GetTempPath(Url)
-        Dim TempFile As New FileInfo(TempPath)
         Dim EnableCache As Boolean = Me.EnableCache
+        Dim TempPath As String = GetTempPath(Source) & If(EnableCache, "", GetUuid()) '不启用缓存时加上随机字符串,避免冲突
+        Dim TempFile As New FileInfo(TempPath)
         If EnableCache AndAlso TempFile.Exists Then
             ActualSource = TempPath
             If (Date.Now - TempFile.LastWriteTime) < FileCacheExpiredTime Then Return '无需刷新缓存
         End If
         RunInNewThread(
         Sub()
-            Dim TempDownloadingPath As String = Nothing
             Try
 RetryStart:
                 '下载
-                ActualSource = LoadingSource '显示加载中图片
-                TempDownloadingPath = TempPath & RandomInteger(0, 10000000)
-                Directory.CreateDirectory(GetPathFromFullPath(TempPath)) '重新实现下载,以避免携带 Header(#5072)
-                Using Client As New Net.WebClient
-                    Client.DownloadFile(Url, TempDownloadingPath)
-                End Using
-                If Url <> Source AndAlso Url <> FallbackSource Then
-                    '已经更换了地址
-                    File.Delete(TempDownloadingPath)
-                ElseIf EnableCache Then
+                ActualSource = LoadingSource '显示加载中的占位图片
+                NetDownloadByLoader(
+                    If(String.IsNullOrEmpty(FallbackSource), {Source}, {Source, FallbackSource}),
+                    TempPath, SimulateBrowserHeaders:=True)
+                If EnableCache Then
                     '保存缓存并显示
-                    If File.Exists(TempPath) Then File.Delete(TempPath)
-                    FileSystem.Rename(TempDownloadingPath, TempPath)
                     RunInUi(Sub() ActualSource = TempPath)
                 Else
                     '直接显示
-                    RunInUiWait(Sub() ActualSource = TempDownloadingPath)
-                    File.Delete(TempDownloadingPath)
+                    RunInUiWait(Sub() ActualSource = TempPath)
+                    File.Delete(TempPath)
                 End If
             Catch ex As Exception
                 Try
                     If TempPath IsNot Nothing Then File.Delete(TempPath)
-                    If TempDownloadingPath IsNot Nothing Then File.Delete(TempDownloadingPath)
                 Catch
                 End Try
-                If Not Retried Then
-                    '更换备用地址
-                    Log(ex, $"下载图片可重试地失败({Url})", LogLevel.Developer)
-                    Retried = True
-                    Url = If(FallbackSource, Source)
-                    '空
-                    If Url Is Nothing Then
-                        ActualSource = Nothing
-                        Return
-                    End If
-                    '本地图片
-                    If Not Url.StartsWithF("http") Then
-                        ActualSource = Url
-                        Return
-                    End If
-                    '从缓存加载网络图片
-                    TempPath = GetTempPath(Url)
-                    TempFile = New FileInfo(TempPath)
-                    If EnableCache AndAlso TempFile.Exists() Then
-                        ActualSource = TempPath
-                        If (Date.Now - TempFile.CreationTime) < FileCacheExpiredTime Then Return '无需刷新缓存
-                    End If
-                    '下载
-                    If Source = Url Then Thread.Sleep(1000) '延迟 1s 重试
-                    GoTo RetryStart
-                Else
-                    Log(ex, $"下载图片失败({Url})", LogLevel.Hint)
-                End If
+                Log(ex, $"下载图片失败", LogLevel.Hint)
             End Try
         End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal)
     End Sub

+ 1 - 5
Plain Craft Launcher 2/Controls/MyLoading.xaml.vb

@@ -79,11 +79,7 @@ Public Class MyLoading
                         Do While Ex.InnerException IsNot Nothing
                             Ex = Ex.InnerException
                         Loop
-                        LabText.Text = StrTrim(Ex.Message)
-                        If {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
-                            "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) LabText.Text.Contains(s)) Then
-                            LabText.Text = "网络环境不佳,请稍后重试,或使用 VPN 以改善网络环境"
-                        End If
+                        LabText.Text = If(Ex.IsNetworkRelated(), "网络环境不佳,请稍后再试,或使用 VPN 改善网络环境", StrTrim(Ex.Message))
                     End If
                 Else
                     LabText.Text = TextError

+ 10 - 27
Plain Craft Launcher 2/Controls/MyPageLeft.vb

@@ -3,28 +3,15 @@
     Private Uuid As Integer = GetUuid()
 
     '执行逐个进入动画的控件
-    Public AnimatedControl As FrameworkElement
-    Public Shared ReadOnly AnimatedControlProperty As DependencyProperty = DependencyProperty.RegisterAttached("AnimatedControl", GetType(String), GetType(MyPageLeft), New PropertyMetadata(Nothing, AddressOf OnAnimatedControlChanged))
-
-    Public Shared Sub SetAnimatedControl(obj As DependencyObject, value As String)
-        obj.SetValue(AnimatedControlProperty, value)
-    End Sub
-
-    Private Shared Sub OnAnimatedControlChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
-        Dim control = TryCast(d, MyPageLeft)
-        If control IsNot Nothing Then
-            Dim handler As RoutedEventHandler
-            handler = Sub(sender, args)
-                          Dim targetName = CStr(e.NewValue)
-                          Dim target = TryCast(control.FindName(targetName), FrameworkElement)
-                          If target IsNot Nothing Then
-                              control.AnimatedControl = target
-                          End If
-                          RemoveHandler control.Loaded, handler
-                      End Sub
-            AddHandler control.Loaded, handler
-        End If
-    End Sub
+    Public Property AnimatedControl As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664)
+        Get
+            Return GetValue(AnimatedControlProperty)
+        End Get
+        Set(value As String)
+            SetValue(AnimatedControlProperty, value)
+        End Set
+    End Property
+    Public Shared ReadOnly AnimatedControlProperty As DependencyProperty = DependencyProperty.Register("AnimatedControl", GetType(String), GetType(MyPageLeft), New PropertyMetadata(Nothing))
 
     Public Sub TriggerShowAnimation()
         If AnimatedControl Is Nothing Then
@@ -97,7 +84,7 @@
     '遍历获取所有需要生成动画的控件
     Private Function GetAllAnimControls(Optional IgnoreInvisibility As Boolean = False) As List(Of FrameworkElement)
         Dim AllControls As New List(Of FrameworkElement)
-        GetAllAnimControls(AnimatedControl, AllControls, IgnoreInvisibility)
+        GetAllAnimControls(FindName(AnimatedControl), AllControls, IgnoreInvisibility)
         Return AllControls
     End Function
     Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
@@ -118,7 +105,3 @@
     End Sub
 
 End Class
-
-Public Interface IRefreshable
-    Sub Refresh()
-End Interface

+ 9 - 22
Plain Craft Launcher 2/Controls/MyPageRight.vb

@@ -3,29 +3,16 @@
     Public PageUuid As Integer = GetUuid()
 
     '“返回顶部” 按钮检测的滚动区域
-    Public PanScroll As MyScrollViewer
+    Public Property PanScroll As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664)
+        Get
+            Return GetValue(PanScrollProperty)
+        End Get
+        Set(value As String)
+            SetValue(PanScrollProperty, value)
+        End Set
+    End Property
     Private Shared ReadOnly PanScrollProperty =
-        DependencyProperty.RegisterAttached("PanScroll", GetType(String), GetType(MyPageRight), New PropertyMetadata(Nothing, AddressOf OnPanScrollChanged))
-
-    Public Shared Sub SetPanScroll(obj As DependencyObject, value As String)
-        obj.SetValue(PanScrollProperty, value)
-    End Sub
-
-    Private Shared Sub OnPanScrollChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
-        Dim control = TryCast(d, MyPageRight)
-        If control IsNot Nothing Then
-            Dim handler As RoutedEventHandler
-            handler = Sub(sender, args)
-                          Dim targetName = CStr(e.NewValue)
-                          Dim target = TryCast(control.FindName(targetName), FrameworkElement)
-                          If target IsNot Nothing Then
-                              control.PanScroll = target
-                          End If
-                          RemoveHandler control.Loaded, handler
-                      End Sub
-            AddHandler control.Loaded, handler
-        End If
-    End Sub
+        DependencyProperty.Register("PanScroll", GetType(String), GetType(MyPageRight), New PropertyMetadata(Nothing))
 
     '当前状态
     Public Enum PageStates

+ 16 - 13
Plain Craft Launcher 2/FormMain.xaml.vb

@@ -96,6 +96,14 @@ Public Class FormMain
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '1:BUG-
+        If LastVersion < 368 Then 'Snapshot 2.10.7
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "新增:允许在版本设置中设置禁止更新 Mod,以防整合包玩家误操作"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:网络与下载稳定性优化"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化:若整合包需要 PCL 不兼容的加载器,允许选择跳过它的安装"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "删除:由于已不再需要,删除手动安装包下载功能"))
+            FeatureCount += 19
+            BugCount += 28
+        End If
         If LastVersion < 366 Then 'Snapshot 2.10.6
             FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:启用 MCIM 社区资源镜像源,以缓解社区资源难以下载的问题"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:正版登录出错时无法给出正确的错误信息"))
@@ -1025,18 +1033,12 @@ Public Class FormMain
     ''' </summary>
     Public Enum PageSubType
         [Default] = 0
-        DownloadInstall = 1
-        DownloadClient = 4
-        DownloadOptiFine = 5
-        DownloadForge = 6
-        DownloadNeoForge = 7
-        DownloadFabric = 8
-        DownloadLiteLoader = 9
-        DownloadMod = 11
-        DownloadPack = 12
-        DownloadDataPack = 13
-        DownloadResourcePack = 14
-        DownloadShader = 15
+        DownloadInstall = 0
+        DownloadMod = 2
+        DownloadPack = 3
+        DownloadDataPack = 4
+        DownloadResourcePack = 5
+        DownloadShader = 6
         SetupLaunch = 0
         SetupUI = 1
         SetupSystem = 2
@@ -1514,7 +1516,8 @@ Public Class FormMain
     End Function
     Private Function BtnExtraBack_GetRealChild() As MyScrollViewer
         If PanMainRight.Child Is Nothing OrElse TypeOf PanMainRight.Child IsNot MyPageRight Then Return Nothing
-        Return CType(PanMainRight.Child, MyPageRight).PanScroll
+        Dim Page As MyPageRight = PanMainRight.Child
+        Return Page.FindName(Page.PanScroll)
     End Function
 
 #End Region

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

@@ -13,13 +13,13 @@ Public Module ModBase
 #Region "声明"
 
     '下列版本信息由更新器自动修改
-    Public Const VersionBaseName As String = "2.10.6" '不含分支前缀的显示用版本名
-    Public Const VersionStandardCode As String = "2.10.6." & VersionBranchCode '标准格式的四段式版本号
+    Public Const VersionBaseName As String = "2.10.7" '不含分支前缀的显示用版本名
+    Public Const VersionStandardCode As String = "2.10.7." & VersionBranchCode '标准格式的四段式版本号
     Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
 #If BETA Then
     Public Const VersionCode As Integer = 367 'Release
 #Else
-    Public Const VersionCode As Integer = 366 'Snapshot
+    Public Const VersionCode As Integer = 368 'Snapshot
 #End If
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -1283,7 +1283,7 @@ Re:
                 Return Nothing
             Catch ex As Exception
                 Log(ex, "检查文件出错")
-                Return GetExceptionSummary(ex)
+                Return ex.GetBrief()
             End Try
         End Function
     End Class
@@ -1329,6 +1329,14 @@ Re:
     ''' </summary>
     Public Function DeleteDirectory(Path As String, Optional IgnoreIssue As Boolean = False) As Integer
         If Not Directory.Exists(Path) Then Return 0
+        If Not Path.EndsWithF("\") Then Path &= "\"
+        If Path.EndsWith(":\") OrElse
+           Path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) & "\" OrElse
+           Path = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) & "\" OrElse
+           Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\" Then '#7030
+            Throw New UnauthorizedAccessException("这是重要的系统文件夹,无法删除:" & Path)
+        End If
+        '实际删除
         Dim DeletedCount As Integer = 0
         Dim Files As String()
         Try
@@ -1364,6 +1372,7 @@ RetryFile:
 RetryDir:
         Try
             Directory.Delete(Path, True)
+        Catch ex As DirectoryNotFoundException
         Catch ex As Exception
             If Not RetriedDir AndAlso Not RunInUi() Then
                 RetriedDir = True
@@ -1420,10 +1429,10 @@ RetryDir:
     Public vbRQ As Char = Convert.ToChar(8221)
 
     ''' <summary>
-    ''' 提取 Exception 的具体描述与堆栈。
+    ''' 提取 Exception 与 Inner Exception 的详细描述与堆栈信息。返回内容总是多于一行。
     ''' </summary>
     ''' <param name="ShowAllStacks">是否必须显示所有堆栈。通常用于判定堆栈信息。</param>
-    Public Function GetExceptionDetail(Ex As Exception, Optional ShowAllStacks As Boolean = False) As String
+    <Extension> Public Function GetDetail(Ex As Exception, Optional ShowAllStacks As Boolean = False) As String
         If Ex Is Nothing Then Return "无可用错误信息!"
 
         '获取最底层的异常
@@ -1450,17 +1459,17 @@ RetryDir:
         Loop
 
         '构造输出信息
-        Dim CommonReason As String = AnalyzeCommonReason(InnerEx, DescList)
-        If CommonReason Is Nothing Then
+        Dim UsualReason As String = AnalyzeUsualReason(InnerEx, Ex, DescList)
+        If UsualReason Is Nothing Then
             Return DescList.Join(vbCrLf)
         Else
-            Return CommonReason & vbCrLf & vbCrLf & "————————————" & vbCrLf & "详细错误信息:" & vbCrLf & DescList.Join(vbCrLf)
+            Return UsualReason & vbCrLf & vbCrLf & "————————————" & vbCrLf & "详细错误信息:" & vbCrLf & DescList.Join(vbCrLf)
         End If
     End Function
     ''' <summary>
-    ''' 提取 Exception 描述,汇总到一行。
+    ''' 提取 Exception 与 Inner Exception 的描述,汇总到一行。
     ''' </summary>
-    Public Function GetExceptionSummary(Ex As Exception) As String
+    <Extension> Public Function GetBrief(Ex As Exception) As String
         If Ex Is Nothing Then Return "无可用错误信息!"
 
         '获取最底层的异常
@@ -1479,28 +1488,27 @@ RetryDir:
         Dim Desc As String = Join(DescList, vbCrLf & "→ ")
 
         '构造输出信息
-        Dim CommonReason As String = AnalyzeCommonReason(InnerEx, DescList)
-        If CommonReason IsNot Nothing Then
-            Return CommonReason & "详细错误:" & DescList.First
+        Dim UsualReason As String = AnalyzeUsualReason(InnerEx, Ex, DescList)
+        If UsualReason IsNot Nothing Then
+            Return UsualReason & "详细错误:" & DescList.First
         Else
             DescList.Reverse() '让最深层错误在最左边
             Return Join(DescList, " ← ")
         End If
     End Function
-    Private Function AnalyzeCommonReason(InnerEx As Exception, DescList As List(Of String)) As String
+    Private Function AnalyzeUsualReason(InnerEx As Exception, OuterEx As Exception, DescList As List(Of String)) As String
         If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is BadImageFormatException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then
             Return "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。"
         ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then
-            Return "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。"
+            Return "PCL 的权限不足。请尝试右键 PCL 选择以管理员身份运行,或将目标从 C 盘或桌面挪到其他硬盘。"
         ElseIf TypeOf InnerEx Is OutOfMemoryException Then
             Return "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。"
         ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then
             Return "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。"
         ElseIf TypeOf InnerEx Is SocketException AndAlso DescList.Any(Function(l) l.Contains("WSAStartup")) Then
             Return "请尝试卸载中国移动云盘,然后再试。"
-        ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
-                "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) DescList.Any(Function(l) l.Contains(s))) Then
-            Return "你的网络环境不佳,导致难以连接到服务器。请稍后重试,或使用 VPN 以改善网络环境。"
+        ElseIf OuterEx.IsNetworkRelated() Then
+            Return "你的网络环境不佳,请稍后再试,或使用 VPN 改善网络环境。"
         Else
             Return Nothing
         End If
@@ -2981,10 +2989,10 @@ Retry:
         If TypeOf Ex Is ThreadInterruptedException Then Return
 
         '获取错误信息
-        Dim ExFull As String = Desc & ":" & GetExceptionDetail(Ex)
+        Dim ExFull As String = $"{Desc}:{Ex.GetDetail()}"
 
         '输出日志
-        Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Desc}:{GetExceptionDetail(Ex, True)}{vbCrLf}" '减轻同步锁占用
+        Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Desc}:{Ex.GetDetail(True)}{vbCrLf}" '减轻同步锁占用
         If ModeDebug Then
             SyncLock LogListLock
                 LogList.Append(AppendText)
@@ -3002,19 +3010,19 @@ Retry:
             Case LogLevel.Normal
 #If DEBUG Then
             Case LogLevel.Developer
-                Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex)
+                Dim ExLine As String = Desc & ":" & Ex.GetBrief()
                 Hint("[开发者模式] " & ExLine, HintType.Info, False)
             Case LogLevel.Debug
-                Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex)
+                Dim ExLine As String = Desc & ":" & Ex.GetBrief()
                 Hint("[调试模式] " & ExLine, HintType.Info, False)
 #Else
             Case LogLevel.Developer
             Case LogLevel.Debug
-                Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex)
+                Dim ExLine As String = Desc & ":" & Ex.GetBrief()
                 If ModeDebug Then Hint("[调试模式] " & ExLine, HintType.Info, False)
 #End If
             Case LogLevel.Hint
-                Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex)
+                Dim ExLine As String = Desc & ":" & Ex.GetBrief()
                 Hint(ExLine, HintType.Critical, False)
             Case LogLevel.Msgbox
                 MyMsgBox(ExFull, Title, IsWarn:=True)
@@ -3093,8 +3101,8 @@ Retry:
                     Url &= "&" & WebUtility.UrlEncode(Datas(i)) & "=" & WebUtility.UrlEncode(Datas(i + 1).Replace(vbCrLf, vbLf))
                 Next
                 NetRequestByClient(Url, MakeLog:=False)
-            Catch
-                Log("[System] 匿名数据上报失败", LogLevel.Debug)
+            Catch ex As Exception
+                Log(ex, "匿名数据上报失败")
             End Try
         End Sub, "Telemetry", ThreadPriority.Lowest)
     End Sub

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

@@ -290,11 +290,12 @@
             '检验输入以确定情况
             If IsForceRestart Then Return True '强制要求重启
             If ((Input Is Nothing) <> (Me.Input Is Nothing)) OrElse (Input IsNot Nothing AndAlso Not Input.Equals(Me.Input)) Then Return True '输入不同
-            If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束
-               (IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '没有超时
-                Return False '则不重试
+            If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束…
+               (IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse
+               LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '…且没有超时…
+                Return False '…则不重试
             Else
-                Return True '需要开始
+                Return True '否则需要重启
             End If
         End Function
         Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)

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

@@ -1,4 +1,7 @@
 Imports System.Net.Http.Headers
+Imports System.Net.Sockets
+Imports System.Runtime.CompilerServices
+Imports System.Security.Policy
 Imports System.Threading.Tasks
 Imports CacheCow.Client
 
@@ -11,15 +14,28 @@ Public Module ModNet
     ''' </summary>
     Public Function NetRequestByClient(Url As String, Optional Method As HttpMethod = Nothing,
             Optional Content As Object = Nothing, Optional ContentType As String = Nothing, Optional Accept As String = "*/*",
-            Optional Timeout As Integer = 25000, Optional Headers As String(,) = Nothing,
-            Optional Encoding As Encoding = Nothing, Optional UseBrowserUserAgent As Boolean = False, Optional MakeLog As Boolean = True) As String
+            Optional Timeout As Integer = 25000, Optional Headers As String(,) = Nothing, Optional RequireJson As Boolean = False,
+            Optional Encoding As Encoding = Nothing, Optional SimulateBrowserHeaders As Boolean = False, Optional MakeLog As Boolean = True) As String
         If Method Is Nothing Then Method = HttpMethod.Get
         If MakeLog Then Log("[Net] 发起网络请求(" & Method.Method & "," & Url & "),最大超时 " & Timeout)
         Try
             Dim HeaderDictionary = If(Headers, {}).ToDictionary
             If ContentType IsNot Nothing Then HeaderDictionary("Content-Type") = ContentType
             If Accept IsNot Nothing Then HeaderDictionary("Accept") = Accept
-            Return If(Encoding, Encoding.UTF8).GetString(SendRequest(Url, Method, Content, HeaderDictionary, Timeout:=Timeout, Encoding:=Encoding, UseBrowserUserAgent:=UseBrowserUserAgent))
+            Dim Result = If(Encoding, Encoding.UTF8).GetString(SendRequest(Url, Method, Content, HeaderDictionary, Timeout:=Timeout, Encoding:=Encoding, SimulateBrowserHeaders:=SimulateBrowserHeaders))
+            '检查结果是否为完整 JSON(在 #6683 中可能返回空,在被 GFW 截断时可能不完整)
+            '在此处检查并报错,可以让 NetRequestByClientRetry 等方法进行重试
+            If RequireJson Then
+                Result = Result.Trim((vbCrLf & " ").ToCharArray) 'Authlib-Injector 的结果会在末尾附带一个空行
+                If Not (Result.StartsWithF("{") AndAlso Result.EndsWithF("}")) AndAlso
+                   Not (Result.StartsWithF("[") AndAlso Result.EndsWithF("]")) Then
+                    Throw New FormatException("返回结果并非 JSON 格式:" &
+                    If(Result.Length > 2000, Result.Substring(0, 1000) & "..." & Result.Substring(Result.Length - 1000), Result))
+                End If
+            End If
+            Return Result
+        Catch ex As FormatException
+            Throw
         Catch ex As ThreadInterruptedException
             Throw
         Catch ex As Exception
@@ -34,8 +50,8 @@ Public Module ModNet
     ''' <param name="BackupUrl">如果第一次尝试失败,换用的备用 URL。</param>
     Public Function NetRequestByClientRetry(Url As String, Optional Method As HttpMethod = Nothing, Optional BackupUrl As String = Nothing,
             Optional Content As Object = Nothing, Optional ContentType As String = Nothing, Optional Accept As String = "*/*",
-            Optional Headers As String(,) = Nothing,
-            Optional Encoding As Encoding = Nothing, Optional UseBrowserUserAgent As Boolean = False) As String
+            Optional Headers As String(,) = Nothing, Optional RequireJson As Boolean = False,
+            Optional Encoding As Encoding = Nothing, Optional SimulateBrowserHeaders As Boolean = False) As String
         Dim RetryCount As Integer = 0
         Dim RetryException As Exception = Nothing
         Dim StartTime As Long = GetTimeTick()
@@ -43,15 +59,15 @@ Public Module ModNet
 Retry:
             Select Case RetryCount
                 Case 0 '正常尝试
-                    Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 10000, Headers, Encoding, UseBrowserUserAgent)
+                    Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 10000, Headers, RequireJson, Encoding, SimulateBrowserHeaders)
                 Case 1 '慢速重试
                     Thread.Sleep(500)
-                    Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 30000, Headers, Encoding, UseBrowserUserAgent)
+                    Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 30000, Headers, RequireJson, Encoding, SimulateBrowserHeaders)
                 Case Else '快速重试
                     If GetTimeTick() - StartTime > 5500 Then
                         '若前两次加载耗费 5 秒以上,才进行重试
                         Thread.Sleep(500)
-                        Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 4000, Headers, Encoding, UseBrowserUserAgent)
+                        Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 4000, Headers, RequireJson, Encoding, SimulateBrowserHeaders)
                     Else
                         Throw RetryException
                     End If
@@ -78,8 +94,8 @@ Retry:
     ''' </summary>
     Public Function NetRequestByClientMultiple(Url As String, Optional Method As HttpMethod = Nothing, Optional ThreadCount As Integer = 3,
             Optional Content As Object = Nothing, Optional ContentType As String = Nothing, Optional Accept As String = "*/*",
-            Optional Headers As String(,) = Nothing, Optional Timeout As Integer = 25000,
-            Optional Encoding As Encoding = Nothing, Optional UseBrowserUserAgent As Boolean = False) As String
+            Optional Headers As String(,) = Nothing, Optional RequireJson As Boolean = False, Optional Timeout As Integer = 25000,
+            Optional Encoding As Encoding = Nothing, Optional SimulateBrowserHeaders As Boolean = False) As String
         Dim Threads As New List(Of Thread)
         Dim RequestResult = Nothing
         Dim RequestEx As Exception = Nothing
@@ -88,7 +104,7 @@ Retry:
             Dim th As New Thread(
             Sub()
                 Try
-                    RequestResult = NetRequestByClient(Url, Method, Content, ContentType, Accept, Timeout, Headers, Encoding, UseBrowserUserAgent)
+                    RequestResult = NetRequestByClient(Url, Method, Content, ContentType, Accept, Timeout, Headers, RequireJson, Encoding, SimulateBrowserHeaders)
                 Catch ex As Exception
                     FailCount += 1
                     RequestEx = ex
@@ -127,16 +143,16 @@ RequestFinished:
     ''' 以多线程下载网页文件的方式获取内容。
     ''' 不支持缓存协商;若需缓存协商,需要换用 NetRequestByClient。
     ''' </summary>
-    Public Function NetRequestByLoader(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String
-        Return NetRequestByLoader({Url}, Timeout, IsJson, UseBrowserUserAgent)
+    Public Function NetRequestByLoader(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional SimulateBrowserHeaders As Boolean = False) As String
+        Return NetRequestByLoader({Url}, Timeout, IsJson, SimulateBrowserHeaders)
     End Function
     ''' <summary>
     ''' 以多线程下载网页文件的方式获取内容。
     ''' 不支持缓存协商;若需缓存协商,需要换用 NetRequestByClient。
     ''' </summary>
-    Public Function NetRequestByLoader(Urls As IEnumerable(Of String), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String
+    Public Function NetRequestByLoader(Urls As IEnumerable(Of String), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional SimulateBrowserHeaders As Boolean = False) As String
         Dim Temp As String = RequestTaskTempFolder() & "download.txt"
-        Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, Temp, New FileChecker With {.IsJson = IsJson}, UseBrowserUserAgent)})
+        Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, Temp, New FileChecker With {.IsJson = IsJson}, SimulateBrowserHeaders)})
         Try
             NewTask.WaitForExitTime(Timeout, TimeoutMessage:="连接服务器超时(第一下载源:" & Urls.First & ")")
             NetRequestByLoader = ReadFile(Temp)
@@ -154,7 +170,7 @@ RequestFinished:
     ''' 通过网络请求直接下载小文件,若文件已存在将被覆盖。
     ''' 不建议用于下载大文件。
     ''' </summary>
-    Public Sub NetDownloadByClient(Url As String, LocalPath As String)
+    Public Sub NetDownloadByClient(Url As String, LocalPath As String, Optional SimulateBrowserHeaders As Boolean = False)
         Log($"[Net] 通过网络请求直接下载小文件:{Url} → {LocalPath}")
         '初始化文件路径
         Try
@@ -165,7 +181,7 @@ RequestFinished:
         End Try
         '下载
         Try
-            File.WriteAllBytes(LocalPath, SendRequest(Url, HttpMethod.Get))
+            File.WriteAllBytes(LocalPath, SendRequest(Url, HttpMethod.Get, SimulateBrowserHeaders:=SimulateBrowserHeaders))
         Catch ex As Exception
             File.Delete(LocalPath)
             Throw New WebException($"通过网络请求直接下载小文件失败({LocalPath})", ex)
@@ -175,16 +191,16 @@ RequestFinished:
     ''' <summary>
     ''' 通过多线程下载引擎下载文件。
     ''' </summary>
-    Public Sub NetDownloadByLoader(Url As String, LocalPath As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False)
-        NetDownloadByLoader({Url}, LocalPath, LoaderToSyncProgress, Check, UseBrowserUserAgent)
+    Public Sub NetDownloadByLoader(Url As String, LocalPath As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional SimulateBrowserHeaders As Boolean = False)
+        NetDownloadByLoader({Url}, LocalPath, LoaderToSyncProgress, Check, SimulateBrowserHeaders)
     End Sub
     ''' <summary>
     ''' 通过多线程下载引擎下载文件。
     ''' </summary>
     ''' <param name="Urls">文件的 Url 列表。</param>
     ''' <param name="LocalPath">下载的本地地址。</param>
-    Public Sub NetDownloadByLoader(Urls As IEnumerable(Of String), LocalPath As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False)
-        Dim NewTask As New LoaderDownload($"文件下载 {GetUuid()}#", New List(Of NetFile) From {New NetFile(Urls, LocalPath, Check, UseBrowserUserAgent)})
+    Public Sub NetDownloadByLoader(Urls As IEnumerable(Of String), LocalPath As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional SimulateBrowserHeaders As Boolean = False)
+        Dim NewTask As New LoaderDownload($"文件下载 {GetUuid()}#", New List(Of NetFile) From {New NetFile(Urls, LocalPath, Check, SimulateBrowserHeaders)})
         Try
             NewTask.WaitForExit(LoaderToSyncProgress:=LoaderToSyncProgress)
         Catch ex As Exception
@@ -201,36 +217,37 @@ RequestFinished:
     '基础请求函数
     Private Function SendRequest(Url As String, Method As HttpMethod,
             Optional Content As Object = Nothing, Optional Headers As Dictionary(Of String, String) = Nothing,
-            Optional UseBrowserUserAgent As Boolean = False, Optional Timeout As Integer = 25000, Optional Encoding As Encoding = Nothing) As Byte()
+            Optional SimulateBrowserHeaders As Boolean = False, Optional Timeout As Integer = 25000, Optional Encoding As Encoding = Nothing) As Byte()
         If RunInUi() AndAlso Not Url.Contains("//127.") Then Throw New Exception("在 UI 线程执行了网络请求")
-        Dim Request As HttpRequestMessage = Nothing, CancelToken As CancellationTokenSource = Nothing, Response As HttpResponseMessage = Nothing
+        Dim Request As HttpRequestMessage = Nothing, CancelToken As CancellationTokenSource = Nothing,
+            Response As HttpResponseMessage = Nothing, HostIp As String = Nothing
         Try
-            Static Client As HttpClient = ClientExtensions.CreateClient(New FileCacheStore.FileStore(PathTemp & "Cache/Http/"), New HttpClientHandler With {
-                .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None,
-                .UseCookies = False '不设为 False 就不能从 Header 手动传入 Cookies
-            })
             Url = SecretCdnSign(Url)
             Request = New HttpRequestMessage(Method, Url)
             '写入 Content
             If Content IsNot Nothing AndAlso Request.Method <> HttpMethod.Get AndAlso Request.Method <> HttpMethod.Head Then
-                Dim Bytes As Byte() = If(TypeOf Content Is Byte(), Content, If(Encoding, Encoding.UTF8).GetBytes(Content.ToString))
-                Request.Content = New ByteArrayContent(Bytes)
-                Request.Content.Headers.ContentLength = Bytes.Length
+                If TypeOf Content Is HttpContent Then
+                    Request.Content = DirectCast(Content, HttpContent)
+                ElseIf TypeOf Content Is Byte() Then
+                    Request.Content = New ByteArrayContent(DirectCast(Content, Byte()))
+                ElseIf Content IsNot Nothing Then
+                    Request.Content = New ByteArrayContent(If(Encoding, Encoding.UTF8).GetBytes(Content.ToString()))
+                End If
             End If
             '写入 Headers
-            If Headers IsNot Nothing Then
-                For Each Header In Headers
-                    If Header.Key.ToLower = "content-type" Then
-                        Request.Content?.Headers.TryAddWithoutValidation(Header.Key, Header.Value)
-                    Else
-                        Request.Headers.Add(Header.Key, Header.Value)
-                    End If
-                Next
-            End If
-            SecretHeadersSign(Url, Request, UseBrowserUserAgent)
-            '发送请求
+            For Each Header In If(Headers, New Dictionary(Of String, String))
+                If Header.Key.ToLower = "content-type" Then
+                    Request.Content?.Headers.TryAddWithoutValidation(Header.Key, Header.Value)
+                Else
+                    Request.Headers.Add(Header.Key, Header.Value)
+                End If
+            Next
+            SecretHeadersSign(Url, Request, SimulateBrowserHeaders)
+            'DNS 解析
             CancelToken = New CancellationTokenSource(Timeout)
-            Response = Client.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult()
+            HostIp = DnsLookup(Request, CancelToken)
+            '发送请求
+            Response = RequestClient.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult()
             Dim ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult()
             Dim ResponseBytes As Byte()
             Using Stream As New MemoryStream
@@ -239,28 +256,111 @@ RequestFinished:
             End Using
             '输出
             If Response.IsSuccessStatusCode Then
+                RecordIPReliability(HostIp, 0.5)
                 Return ResponseBytes
             Else
+                RecordIPReliability(HostIp, -0.7)
                 Dim ResponseMessage = If(Encoding, Encoding.UTF8).GetString(ResponseBytes)
                 Throw New ResponsedWebException(
-                    $"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Method},{Url}" &
+                    $"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Method},{Url},{HostIp}" &
                     If(String.IsNullOrEmpty(ResponseMessage), "", vbCrLf & ResponseMessage), ResponseMessage)
             End If
         Catch ex As ThreadInterruptedException
             Throw
         Catch ex As ResponsedWebException
             Throw
-        Catch ex As TaskCanceledException 'CancellationTokenSource 超时
-            Throw New WebException($"连接服务器超时,请检查你的网络环境是否良好({Method}, {Url})", WebExceptionStatus.Timeout)
+        Catch ex As DnsLookupException
+            Throw
         Catch ex As Exception
-            Throw New Exception($"网络请求出现意外异常({Method}, {Url})", ex)
+            RecordIPReliability(HostIp, -1)
+            If TypeOf ex Is OperationCanceledException Then 'CancellationToken 超时
+                Throw New WebException($"连接服务器超时,请稍后再试,或使用 VPN 改善网络环境({Method}, {Url},{HostIp})", WebExceptionStatus.Timeout)
+            Else
+                Throw New Exception($"网络请求出现意外异常({Method}, {Url},{HostIp})", ex)
+            End If
         Finally
             Request?.Dispose()
             CancelToken?.Dispose()
             Response?.Dispose()
         End Try
     End Function
+    Private RequestClient As HttpClient = ClientExtensions.CreateClient(New FileCacheStore.FileStore(PathTemp & "Cache/Http/"), New HttpClientHandler With {
+        .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None,
+        .UseCookies = False '不设为 False 就不能从 Header 手动传入 Cookies
+    })
+
+    ''' <summary>
+    ''' 进行 DNS 解析。它仅在选择的 IP 与系统默认的不一致时才对 URL 中的 Host 进行替换。
+    ''' 返回请求时应使用的 IP;若为 IPv6,则加上了方括号。
+    ''' </summary>
+    ''' <exception cref="DnsLookupException"></exception>
+    Private Function DnsLookup(Request As HttpRequestMessage, CancelToken As CancellationTokenSource) As String
+        Dim GetIPReliability = Function(Key) IPReliability.GetOrDefault(Key.ToString, 0)
+        '初步 DNS 解析
+        Dim Host = Request.RequestUri.Host
+        Dim DnsTask As Task(Of IPAddress()) = Nothing
+        Try
+            DnsTask = Dns.GetHostAddressesAsync(Host)
+            DnsTask.Wait(CancelToken.Token)
+        Catch ex As Exception
+            Throw New DnsLookupException($"DNS 解析失败,请检查你的网络连接({Host})", ex)
+        End Try
+        Dim Candidates As IPAddress() = DnsTask.Result.ToArray
+        If Not Candidates.Any Then Throw New DnsLookupException($"DNS 解析无结果,请检查你的网络连接({Host})")
+        '若同时存在 IPv4 和 IPv6 地址,仅选择其中一类(因为 GFW 可能只屏蔽了 IPv4 或 IPv6)
+        Dim IPv4Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetwork).ToArray
+        Dim IPv6Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetworkV6).ToArray
+        If IPv4Targets.Any AndAlso IPv6Targets.Any Then
+            Dim IPv4Reliability = IPv4Targets.Average(Function(i) GetIPReliability(i))
+            Dim IPv6Reliability = IPv6Targets.Average(Function(i) GetIPReliability(i))
+            If Host = "api.modrinth.com" Then IPv6Reliability -= 0.1 '让 Modrinth 优先使用 IPv4 地址(#6887)
+            Candidates = If(IPv4Reliability >= IPv6Reliability, IPv4Targets, IPv6Targets)
+        End If
+        '选择可靠度最高的 IP
+        Dim MaxReliability As Double = Candidates.Max(Function(i) GetIPReliability(i))
+        Dim Target As IPAddress = Candidates.First(Function(i) GetIPReliability(i) = MaxReliability)
+        Dim TargetIp As String = If(Target.AddressFamily = AddressFamily.InterNetworkV6, $"[{Target}]", Target.ToString)
+        If Target IsNot Candidates.First Then
+            '优选结果与系统默认 IP 不一致,替换域名并设置 Host 头
+            If ModeDebug Then Log($"[Net] DNS 结果:{Host} → {Target},所有可能的 IP 与可靠度:{DnsTask.Result.Select(Function(i) $"{i} → {GetIPReliability(i):0.000}").Join(";")}")
+            Request.Headers.Host = Host
+            Dim Builder As New UriBuilder(Request.RequestUri)
+            Builder.Host = TargetIp
+            Request.RequestUri = Builder.Uri
+        End If
+        If Not IPReliability.ContainsKey(TargetIp) Then IPReliability(TargetIp) = -0.01 '预先降低一点,这样快速的重复请求会使用不同的 IP 以提高成功率
+        Return TargetIp
+    End Function
+    ''' <summary>
+    ''' 记录每个 IP 地址的请求可靠度。
+    ''' 通常取值 -1 ~ +0.5,越高越好。未尝试过的 IP 应视为 0。
+    ''' </summary>
+    Private IPReliability As New Dictionary(Of String, Double)
+    ''' <summary>
+    ''' 根据请求结果,记录 IP 地址的可靠度。
+    ''' </summary>
+    Private Sub RecordIPReliability(IP As String, Result As Double)
+        If IP Is Nothing Then Return
+        If Not IPReliability.ContainsKey(IP) Then IPReliability(IP) = 0
+        IPReliability(IP) = (IPReliability(IP) + Result) / 2
+    End Sub
 
+    ''' <summary>
+    ''' DNS 解析失败引发的异常。
+    ''' </summary>
+    Private Class DnsLookupException
+        Inherits Exception
+        Public Sub New(Message As String, Optional InnerException As Exception = Nothing)
+            MyBase.New(Message, InnerException)
+        End Sub
+        Public Sub New(Message As String)
+            MyBase.New(Message)
+        End Sub
+    End Class
+    ''' <summary>
+    ''' 当网络请求失败时引发的异常。
+    ''' 附带 Response 属性,可用于获取远程服务器给予的回复。
+    ''' </summary>
     Public Class ResponsedWebException
         Inherits WebException
         ''' <summary>
@@ -277,6 +377,10 @@ RequestFinished:
 
 #Region "多线程下载引擎"
 
+    Private ThreadClient As New HttpClient(New HttpClientHandler With {
+        .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None
+    })
+
     ''' <summary>
     ''' 最大线程数。
     ''' </summary>
@@ -581,12 +685,12 @@ RequestFinished:
             End SyncLock
         End Function
         ''' <summary>
-        ''' 是否已经没有可用源了。
+        ''' 是否存在可用源。
         ''' </summary>
         Public Function HasAvailableSource(Optional AllowOnceSource As Boolean = True) As Boolean
             SyncLock LockSource
-                If AllowOnceSource AndAlso SourcesOnce.Any Then Return True
-                If Sources.Any(Function(s) Not s.IsFailed) Then Return True
+                If Sources.Any(Function(s) Not s.IsFailed) Then Return True '存在多线程可用源
+                If AllowOnceSource AndAlso SourcesOnce.Any Then Return True '存在单线程可用源
             End SyncLock
             Return False
         End Function
@@ -648,7 +752,7 @@ RequestFinished:
         ''' <summary>
         ''' 下载时是否添加浏览器 UA。
         ''' </summary>
-        Public UseBrowserUserAgent As Boolean
+        Public SimulateBrowserHeaders As Boolean
 
         ''' <summary>
         ''' 上次记速时的时间。
@@ -746,7 +850,7 @@ RequestFinished:
         ''' 新建一个需要下载的文件。
         ''' </summary>
         ''' <param name="LocalPath">包含文件名的本地地址。</param>
-        Public Sub New(Urls As IEnumerable(Of String), LocalPath As String, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False)
+        Public Sub New(Urls As IEnumerable(Of String), LocalPath As String, Optional Check As FileChecker = Nothing, Optional SimulateBrowserHeaders As Boolean = False)
             Dim Sources As New List(Of NetSource)
             Dim Count As Integer = 0
             Urls = Urls.Distinct.ToArray
@@ -757,7 +861,7 @@ RequestFinished:
             Me.Sources = Sources
             Me.LocalPath = LocalPath
             Me.Check = Check
-            Me.UseBrowserUserAgent = UseBrowserUserAgent
+            Me.SimulateBrowserHeaders = SimulateBrowserHeaders
             Me.LocalName = GetFileNameFromPath(LocalPath)
         End Sub
 
@@ -768,17 +872,17 @@ RequestFinished:
         Public Function TryBeginThread() As NetThread
             Try
 
-                '条件检测
-                If NetTaskThreadCount >= NetTaskThreadLimit OrElse Not HasAvailableSource() OrElse
-                    (IsNoSplit AndAlso Threads IsNot Nothing AndAlso Threads.State <> NetState.Error) Then Return Nothing
-                If State >= NetState.Merge OrElse State = NetState.WaitForCheck Then Return Nothing
-                SyncLock LockState
-                    If State < NetState.Connect Then State = NetState.Connect
-                End SyncLock
-                '初始化参数
                 Dim StartPosition As Long, StartSource As NetSource = Nothing
                 Dim Th As Thread, ThreadInfo As NetThread
-                SyncLock LockChain
+                '条件检测
+                SyncLock LockSource
+                    If NetTaskThreadCount >= NetTaskThreadLimit OrElse Not HasAvailableSource() OrElse
+                        (IsNoSplit AndAlso Threads IsNot Nothing AndAlso Threads.State <> NetState.Error) Then Return Nothing
+                    If State >= NetState.Merge OrElse State = NetState.WaitForCheck Then Return Nothing
+                    SyncLock LockState
+                        If State < NetState.Connect Then State = NetState.Connect
+                    End SyncLock
+                    '初始化参数
                     '获取线程起点与下载源
                     '不分割
                     If IsNoSplit Then GoTo StartSingleThreadDownload
@@ -789,6 +893,8 @@ StartSingleThreadDownload:
                         Reset()
                         State = NetState.Get
                     End If
+                End SyncLock
+                SyncLock LockChain
                     '首个开始点
                     If Threads Is Nothing Then
                         StartPosition = 0
@@ -805,7 +911,9 @@ StartSingleThreadDownload:
                         End If
                     Next
                     '是否禁用多线程,以及规定碎片大小
-                    Dim TargetUrl As String = GetSource().Url
+                    Dim Source As NetSource = GetSource()
+                    If Source Is Nothing Then Return Nothing
+                    Dim TargetUrl As String = Source.Url
                     If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("bmclapi") OrElse TargetUrl.Contains("github.com") OrElse
                        TargetUrl.Contains("optifine.net") OrElse TargetUrl.Contains("modrinth") OrElse TargetUrl.Contains("momot.rs") Then Return Nothing
                     '寻找最大碎片
@@ -850,7 +958,7 @@ StartThread:
                 Return ThreadInfo
 
             Catch ex As Exception
-                Log(ex, "尝试开始下载线程失败(" & If(LocalName, "Nothing") & ")", LogLevel.Hint)
+                Fail(New Exception($"尝试开始下载线程失败({LocalName})", ex))
                 Return Nothing
             End Try
         End Function
@@ -859,7 +967,9 @@ StartThread:
         ''' </summary>
         Private Sub Thread(Th As NetThread)
             If ModeDebug OrElse Th.DownloadStart = 0 Then Log($"[Download] {LocalName}:开始,起始点 {Th.DownloadStart},{Th.Source.Url}")
-            Dim ResultStream As Stream = Nothing
+            Dim ResultStream As Stream = Nothing, HttpRequest As HttpRequestMessage = Nothing,
+                Response As HttpResponseMessage = Nothing, ResponseStream As Stream = Nothing,
+                CancelToken As CancellationTokenSource = Nothing, HostIp As String = ""
             '部分下载源真的特别慢,并且只需要一个请求,例如 Ping 为 20s,如果增长太慢,就会造成类似 2.5s 5s 7.5s 10s 12.5s... 的极大延迟
             '延迟过长会导致某些特别慢的链接迟迟不被掐死
             Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 6000) * (1 + Th.Source.FailCount), 30000)
@@ -867,145 +977,140 @@ StartThread:
             Try
                 Dim HttpDataCount As Integer = 0
                 If SourcesOnce.Contains(Th.Source) AndAlso Th <> Th.Source.SingleThread Then GoTo SourceBreak
-                Dim HttpRequest As New HttpRequestMessage(HttpMethod.Get, Th.Source.Url)
-                SecretHeadersSign(Th.Source.Url, HttpRequest, UseBrowserUserAgent)
+                HttpRequest = New HttpRequestMessage(HttpMethod.Get, Th.Source.Url)
+                SecretHeadersSign(Th.Source.Url, HttpRequest, SimulateBrowserHeaders)
+                CancelToken = New CancellationTokenSource(Timeout)
+                HostIp = DnsLookup(HttpRequest, CancelToken) 'DNS 预解析
                 If Not Th.IsFirstThread Then HttpRequest.Headers.Range = New RangeHeaderValue(Th.DownloadStart, Nothing)
                 Dim ContentLength As Long = 0
-                Using CancelToken As New CancellationTokenSource(Timeout)
-                    Static Client As New HttpClient(New HttpClientHandler With {
-                        .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None
-                    })
-                    Using Response As HttpResponseMessage = Client.SendAsync(HttpRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult()
-                        If Not Response.IsSuccessStatusCode Then Throw New Exception($"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Th.Source.Url}") '状态码检查
-                        If State = NetState.Error Then GoTo SourceBreak '快速中断
-                        If ModeDebug AndAlso Response.RequestMessage.RequestUri.ToString <> Th.Source.Url Then Log($"[Download] {LocalName}:重定向至 {Response.RequestMessage.RequestUri}")
-                        '文件大小校验
-                        ContentLength = Response.Content.Headers.ContentLength.GetValueOrDefault(-1)
-                        If ContentLength = -1 Then
-                            If FileSize > 1 Then
-                                If Th.DownloadStart = 0 Then
-                                    Log($"[Download] {LocalName}:文件大小未知,但已从其他下载源获取,不作处理")
-                                Else
-                                    Log($"[Download] {LocalName}:ContentLength 返回了 -1,无法确定是否支持分段下载,视作不支持")
-                                    GoTo NotSupportRange
-                                End If
-                            Else
-                                FileSize = -1 : IsUnknownSize = True
-                                Log($"[Download] {LocalName}:文件大小未知")
-                            End If
-                        ElseIf ContentLength < 0 Then
-                            Throw New Exception("获取片大小失败,结果为 " & ContentLength & "。")
-                        ElseIf Th.IsFirstThread Then
-                            If Check IsNot Nothing Then
-                                If ContentLength < Check.MinSize AndAlso Check.MinSize > 0 Then
-                                    Throw New Exception($"文件大小不足,获取结果为 {ContentLength},要求至少为 {Check.MinSize}。")
-                                End If
-                                If ContentLength <> Check.ActualSize AndAlso Check.ActualSize > 0 Then
-                                    Throw New Exception($"文件大小不一致,获取结果为 {ContentLength},要求必须为 {Check.ActualSize}。")
-                                End If
-                            End If
-                            FileSize = ContentLength : IsUnknownSize = False
-                            Log($"[Download] {LocalName}:文件大小 {ContentLength}({GetString(ContentLength)})")
-                            '若文件大小大于 50 M,进行剩余磁盘空间校验
-                            If ContentLength > 50 * 1024 * 1024 Then
-                                For Each Drive As DriveInfo In DriveInfo.GetDrives
-                                    Dim DriveName As String = Drive.Name.First.ToString
-                                    Dim RequiredSpace = If(PathTemp.StartsWithF(DriveName), ContentLength * 1.1, 0) +
-                                                    If(LocalPath.StartsWithF(DriveName), ContentLength + 5 * 1024 * 1024, 0)
-                                    If Drive.TotalFreeSpace < RequiredSpace Then
-                                        Throw New Exception(DriveName & " 盘空间不足,无法进行下载。" & vbCrLf & "需要至少 " & GetString(RequiredSpace) & " 空间,但当前仅剩余 " & GetString(Drive.TotalFreeSpace) & "。" &
-                                                        If(PathTemp.StartsWithF(DriveName), vbCrLf & vbCrLf & "下载时需要与文件同等大小的空间存放缓存,你可以在设置中调整缓存文件夹的位置。", ""))
-                                    End If
-                                Next
+                Response = ThreadClient.SendAsync(HttpRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult()
+                If Not Response.IsSuccessStatusCode Then Throw New Exception($"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Th.Source.Url}") '状态码检查
+                If State = NetState.Error Then GoTo SourceBreak '快速中断
+                If ModeDebug AndAlso Response.RequestMessage.RequestUri.ToString <> Th.Source.Url Then Log($"[Download] {LocalName}:重定向至 {Response.RequestMessage.RequestUri}")
+                '文件大小校验
+                ContentLength = Response.Content.Headers.ContentLength.GetValueOrDefault(-1)
+                If ContentLength = -1 Then
+                    If FileSize > 1 Then
+                        If Th.DownloadStart = 0 Then
+                            Log($"[Download] {LocalName}:文件大小未知,但已从其他下载源获取,不作处理")
+                        Else
+                            Log($"[Download] {LocalName}:ContentLength 返回了 -1,无法确定是否支持分段下载,视作不支持")
+                            GoTo NotSupportRange
+                        End If
+                    Else
+                        FileSize = -1 : IsUnknownSize = True
+                        Log($"[Download] {LocalName}:文件大小未知")
+                    End If
+                ElseIf ContentLength < 0 Then
+                    Throw New Exception("获取片大小失败,结果为 " & ContentLength & "。")
+                ElseIf Th.IsFirstThread Then
+                    If Check IsNot Nothing Then
+                        If ContentLength < Check.MinSize AndAlso Check.MinSize > 0 Then
+                            Throw New Exception($"文件大小不足,获取结果为 {ContentLength},要求至少为 {Check.MinSize}。")
+                        End If
+                        If ContentLength <> Check.ActualSize AndAlso Check.ActualSize > 0 Then
+                            Throw New Exception($"文件大小不一致,获取结果为 {ContentLength},要求必须为 {Check.ActualSize}。")
+                        End If
+                    End If
+                    FileSize = ContentLength : IsUnknownSize = False
+                    Log($"[Download] {LocalName}:文件大小 {ContentLength}({GetString(ContentLength)})")
+                    '若文件大小大于 50 M,进行剩余磁盘空间校验
+                    If ContentLength > 50 * 1024 * 1024 Then
+                        For Each Drive As DriveInfo In DriveInfo.GetDrives
+                            Dim DriveName As String = Drive.Name.First.ToString
+                            Dim RequiredSpace = If(PathTemp.StartsWithF(DriveName), ContentLength * 1.1, 0) +
+                                                If(LocalPath.StartsWithF(DriveName), ContentLength + 5 * 1024 * 1024, 0)
+                            If Drive.TotalFreeSpace < RequiredSpace Then
+                                Throw New Exception(DriveName & " 盘空间不足,无法进行下载。" & vbCrLf & "需要至少 " & GetString(RequiredSpace) & " 空间,但当前仅剩余 " & GetString(Drive.TotalFreeSpace) & "。" &
+                                                    If(PathTemp.StartsWithF(DriveName), vbCrLf & vbCrLf & "下载时需要与文件同等大小的空间存放缓存,你可以在设置中调整缓存文件夹的位置。", ""))
                             End If
-                        ElseIf FileSize < 0 Then
-                            Throw New Exception("非首线程运行时,尚未获取文件大小")
-                        ElseIf Th.DownloadStart > 0 AndAlso ContentLength = FileSize Then
+                        Next
+                    End If
+                ElseIf FileSize < 0 Then
+                    Throw New Exception("非首线程运行时,尚未获取文件大小")
+                ElseIf Th.DownloadStart > 0 AndAlso ContentLength = FileSize Then
 NotSupportRange:
-                            SyncLock LockSource
-                                If SourcesOnce.Contains(Th.Source) Then
-                                    GoTo SourceBreak
-                                Else
-                                    SourcesOnce.Add(Th.Source)
-                                End If
+                    SyncLock LockSource
+                        If SourcesOnce.Contains(Th.Source) Then
+                            GoTo SourceBreak
+                        Else
+                            SourcesOnce.Add(Th.Source)
+                        End If
+                    End SyncLock
+                    Throw New WebException($"该下载源不支持分段下载:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}")
+                ElseIf Not FileSize - Th.DownloadStart = ContentLength Then
+                    Throw New WebException($"获取到的分段大小不一致:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}")
+                End If
+                'Log($"[Download] {LocalName} {Info.Uuid}#:通过大小检查,文件大小 {FileSize},起始点 {Info.DownloadStart},ContentLength {ContentLength}")
+                Th.State = NetState.Get
+                SyncLock LockState
+                    If State < NetState.Get Then State = NetState.Get
+                End SyncLock
+                '创建缓存文件
+                If IsNoSplit Then
+                    Th.Temp = Nothing
+                    Cache = New MemoryStream
+                    ResultStream = Cache
+                Else
+                    Th.Temp = $"{PathTemp}Download\{Uuid}_{Th.Uuid}_{RandomInteger(0, 999999)}.tmp"
+                    ResultStream = New FileStream(Th.Temp, FileMode.Create, FileAccess.Write, FileShare.Read)
+                End If
+                '开始下载
+                ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult()
+                If Setup.Get("SystemDebugDelay") Then Threading.Thread.Sleep(RandomInteger(50, 3000))
+                Dim ResponseBytes As Byte() = New Byte(16384) {}
+                HttpDataCount = ResponseStream.ReadAsync(ResponseBytes, 0, 16384, CancelToken.Token).GetAwaiter().GetResult()
+                While (IsUnknownSize OrElse Th.DownloadUndone > 0) AndAlso '判断是否下载完成
+                            HttpDataCount > 0 AndAlso Not IsProgramEnded AndAlso State < NetState.Merge AndAlso (Not Th.Source.IsFailed OrElse Th.Source.SingleThread = Th)
+                    '限速
+                    While NetTaskSpeedLimitHigh > 0 AndAlso NetTaskSpeedLimitLeft <= 0
+                        Threading.Thread.Sleep(16)
+                    End While
+                    Dim RealDataCount As Integer = If(IsUnknownSize, HttpDataCount, Math.Min(HttpDataCount, Th.DownloadUndone))
+                    SyncLock NetTaskSpeedLimitLeftLock
+                        If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount
+                    End SyncLock
+                    Dim DeltaTime = GetTimeTick() - Th.LastReceiveTime
+                    If DeltaTime > 1000000 Then DeltaTime = 1 '避免时间刻反转导致出现极大值
+                    If RealDataCount > 0 Then
+                        '有数据
+                        If Th.DownloadDone = 0 Then
+                            '第一次接受到数据
+                            Th.State = NetState.Download
+                            SyncLock LockState
+                                If State < NetState.Download Then State = NetState.Download
+                            End SyncLock
+                            SyncLock LockCount
+                                ConnectCount += 1
+                                ConnectTime += GetTimeTick() - Th.InitTime
                             End SyncLock
-                            Throw New WebException($"该下载源不支持分段下载:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}")
-                        ElseIf Not FileSize - Th.DownloadStart = ContentLength Then
-                            Throw New WebException($"获取到的分段大小不一致:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}")
                         End If
-                        'Log($"[Download] {LocalName} {Info.Uuid}#:通过大小检查,文件大小 {FileSize},起始点 {Info.DownloadStart},ContentLength {ContentLength}")
-                        Th.State = NetState.Get
-                        SyncLock LockState
-                            If State < NetState.Get Then State = NetState.Get
+                        SyncLock LockCount
+                            Th.Source.FailCount = 0
+                            For Each Task In Tasks
+                                Task.FailCount = 0
+                            Next
                         End SyncLock
-                        '创建缓存文件
-                        If IsNoSplit Then
-                            Th.Temp = Nothing
-                            Cache = New MemoryStream
-                            ResultStream = Cache
-                        Else
-                            Th.Temp = $"{PathTemp}Download\{Uuid}_{Th.Uuid}_{RandomInteger(0, 999999)}.tmp"
-                            ResultStream = New FileStream(Th.Temp, FileMode.Create, FileAccess.Write, FileShare.Read)
+                        NetManager.DownloadDone += RealDataCount
+                        SyncLock LockDone
+                            DownloadDone += RealDataCount
+                        End SyncLock
+                        Th.DownloadDone += RealDataCount
+                        ResultStream.Write(ResponseBytes, 0, RealDataCount)
+                        '已完成
+                        If Th.DownloadUndone = 0 AndAlso Not IsUnknownSize Then Exit While
+                        '检查速度是否过慢
+                        If DeltaTime > 5000 AndAlso DeltaTime > RealDataCount AndAlso '数据包间隔大于 5s,且速度小于 1K/s
+                                    Th.Source.SingleThread Is Nothing Then '且并非单线程下载
+                            Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。")
                         End If
-                        '开始下载
-                        Using ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult()
-                            If Setup.Get("SystemDebugDelay") Then Threading.Thread.Sleep(RandomInteger(50, 3000))
-                            Dim ResponseBytes As Byte() = New Byte(16384) {}
-                            HttpDataCount = ResponseStream.ReadAsync(ResponseBytes, 0, 16384, CancelToken.Token).GetAwaiter().GetResult()
-                            While (IsUnknownSize OrElse Th.DownloadUndone > 0) AndAlso '判断是否下载完成
-                                HttpDataCount > 0 AndAlso Not IsProgramEnded AndAlso State < NetState.Merge AndAlso (Not Th.Source.IsFailed OrElse Th.Source.SingleThread = Th)
-                                '限速
-                                While NetTaskSpeedLimitHigh > 0 AndAlso NetTaskSpeedLimitLeft <= 0
-                                    Threading.Thread.Sleep(16)
-                                End While
-                                Dim RealDataCount As Integer = If(IsUnknownSize, HttpDataCount, Math.Min(HttpDataCount, Th.DownloadUndone))
-                                SyncLock NetTaskSpeedLimitLeftLock
-                                    If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount
-                                End SyncLock
-                                Dim DeltaTime = GetTimeTick() - Th.LastReceiveTime
-                                If DeltaTime > 1000000 Then DeltaTime = 1 '时间刻反转导致出现极大值
-                                If RealDataCount > 0 Then
-                                    '有数据
-                                    If Th.DownloadDone = 0 Then
-                                        '第一次接受到数据
-                                        Th.State = NetState.Download
-                                        SyncLock LockState
-                                            If State < NetState.Download Then State = NetState.Download
-                                        End SyncLock
-                                        SyncLock LockCount
-                                            ConnectCount += 1
-                                            ConnectTime += GetTimeTick() - Th.InitTime
-                                        End SyncLock
-                                    End If
-                                    SyncLock LockCount
-                                        Th.Source.FailCount = 0
-                                        For Each Task In Tasks
-                                            Task.FailCount = 0
-                                        Next
-                                    End SyncLock
-                                    NetManager.DownloadDone += RealDataCount
-                                    SyncLock LockDone
-                                        DownloadDone += RealDataCount
-                                    End SyncLock
-                                    Th.DownloadDone += RealDataCount
-                                    ResultStream.Write(ResponseBytes, 0, RealDataCount)
-                                    '检查速度是否过慢
-                                    If DeltaTime > 1500 AndAlso DeltaTime > RealDataCount AndAlso '数据包间隔大于 1.5s,且速度小于 1.5K/s
-                                        Th.Source.SingleThread Is Nothing Then '并非单线程下载
-                                        Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。")
-                                    End If
-                                    Th.LastReceiveTime = GetTimeTick()
-                                    '已完成
-                                    If Th.DownloadUndone = 0 AndAlso Not IsUnknownSize Then Exit While
-                                ElseIf Th.LastReceiveTime > 0 AndAlso DeltaTime > Timeout Then
-                                    '无数据,且已超时
-                                    Throw New TimeoutException("操作超时,无数据。")
-                                End If
-                                HttpDataCount = ResponseStream.Read(ResponseBytes, 0, 16384)
-                            End While
-                        End Using
-                    End Using
-                End Using
+                        Th.LastReceiveTime = GetTimeTick()
+                    ElseIf Th.LastReceiveTime > 0 AndAlso DeltaTime > Timeout Then
+                        '无数据,且已超时
+                        Throw New TimeoutException("操作超时,无数据。")
+                    End If
+                    HttpDataCount = ResponseStream.Read(ResponseBytes, 0, 16384)
+                End While
 SourceBreak:
                 If State = NetState.Error OrElse (Th.Source.IsFailed AndAlso Th.Source.SingleThread <> Th) OrElse (Th.DownloadUndone > 0 AndAlso Not IsUnknownSize) Then
                     '被外部中断
@@ -1018,16 +1123,24 @@ SourceBreak:
                     '本线程完成
                     Th.State = NetState.Finish
                     If ModeDebug Then Log($"[Download] {LocalName}:完成,已下载 {Th.DownloadDone}({Th.DownloadStart}~{Th.DownloadEnd})")
+                    RecordIPReliability(HostIp, 0.5)
                 End If
             Catch ex As Exception
-                Log($"[Download] {LocalName}:出错,{If(TypeOf ex Is TaskCanceledException, $"已超时({Timeout}ms)", GetExceptionDetail(ex))}")
+                Log($"[Download] {LocalName}:出错,{If(TypeOf ex Is OperationCanceledException, $"已超时({Timeout}ms)", ex.GetDetail())},{HostIp}")
+                RecordIPReliability(HostIp, -0.7)
                 SourceFail(Th, ex, False)
             Finally
+                '释放资源
+                HttpRequest?.Dispose()
+                Response?.Dispose()
+                ResponseStream?.Dispose()
+                CancelToken?.Dispose()
+                If Not IsNoSplit Then ResultStream?.Dispose()
+                '改变计数
                 SyncLock NetTaskThreadCountLock
                     NetTaskThreadCount -= 1
                 End SyncLock
                 '合并
-                If Not IsNoSplit Then ResultStream?.Dispose()
                 If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso
                     State < NetState.Merge AndAlso Th.State <> NetState.Error Then Merge(Th)
             End Try
@@ -1046,7 +1159,7 @@ SourceBreak:
             If IsMergeFailure OrElse
                ex.Message.Contains("该下载源不支持") OrElse ex.Message.Contains("未能解析") OrElse ex.Message.Contains("(404)") OrElse
                ex.Message.Contains("(502)") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse ex.Message.Contains("获取到的分段大小不一致") OrElse
-               (ex.Message.Contains("(403)") AndAlso Not Th.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403,所以不应因此禁用下载源
+               ((ex.Message.Contains("(403)") OrElse ex.Message.Contains("(429)")) AndAlso Not Th.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403/429,所以不应因此禁用下载源
                (Th.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse Th.Source.FailCount > NetTaskThreadLimit + 2 Then
                 '当一个下载源有多个线程在下载时,只选择其中一个线程进行后续处理
                 Dim IsThisFail As Boolean = False
@@ -1140,10 +1253,10 @@ SourceBreak:
             Dim RetryCount As Integer = 0
             Try
 Retry:
+                '创建文件夹
+                If File.Exists(LocalPath) Then File.Delete(LocalPath)
+                Directory.CreateDirectory(GetPathFromFullPath(LocalPath))
                 SyncLock LockChain
-                    '创建文件夹
-                    If File.Exists(LocalPath) Then File.Delete(LocalPath)
-                    Directory.CreateDirectory(GetPathFromFullPath(LocalPath))
                     '合并文件
                     If IsNoSplit Then
                         '仅有一个线程,从缓存中输出
@@ -1386,13 +1499,13 @@ NextElement:
                         If File Is Nothing Then Throw New ArgumentException("存在空文件请求!")
                         For Each Source As NetSource In File.Sources
                             If Not (Source.Url.StartsWithF("https://", True) OrElse Source.Url.StartsWithF("http://", True)) Then
-                                Source.Ex = New ArgumentException("输入的下载链接不正确!")
+                                Source.Ex = New ArgumentException("输入的下载链接不正确:" & Source.Url)
                                 Source.IsFailed = True
                             End If
                         Next
                         If Not File.HasAvailableSource() Then Throw New ArgumentException("输入的下载链接不正确!")
-                        If Not File.LocalPath.ToLower.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确!")
-                        If File.LocalPath.EndsWithF("\") Then Throw New ArgumentException("请输入含文件名的完整文件路径!")
+                        If Not File.LocalPath.ToLower.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确:" & File.LocalPath)
+                        If File.LocalPath.EndsWithF("\") Then Throw New ArgumentException("请输入含文件名的完整文件路径:" & File.LocalPath)
                         '文件夹检测
                         Dim DirPath As String = New FileInfo(File.LocalPath).Directory.FullName
                         If Not Directory.Exists(DirPath) Then Directory.CreateDirectory(DirPath)
@@ -1522,12 +1635,12 @@ Retry:
                 If State > LoadState.Loading Then Return
                 If ExList Is Nothing OrElse Not ExList.Any() Then ExList = New List(Of Exception) From {New Exception("未知错误!")}
                 '寻找有效的错误信息
-                Dim UsefulExs = ExList.Where(Function(e) TypeOf e IsNot TaskCanceledException AndAlso TypeOf e IsNot ThreadInterruptedException).ToList
+                Dim UsefulExs = ExList.Where(Function(e) TypeOf e IsNot OperationCanceledException AndAlso TypeOf e IsNot ThreadInterruptedException).ToList
                 [Error] = If(UsefulExs.FirstOrDefault, ExList.FirstOrDefault)
                 '获取实际失败的文件
                 For Each File In Files
                     If File.State <> NetState.Error Then Continue For
-                    Dim Detail As String = Join(File.Sources.Select(Function(s) $"{If(s.Ex?.Message, "无错误信息。")}({s.Url})"), vbCrLf)
+                    Dim Detail As String = Join(File.Sources.Select(Function(s) $"{If(s.Ex Is Nothing, "无错误信息。", s.Ex.GetBrief())}({s.Url})"), vbCrLf)
                     [Error] = New Exception("文件下载失败:" & File.LocalPath & vbCrLf &
                                             "各下载源的错误如下:" & vbCrLf & Detail,
                                             [Error])
@@ -1547,7 +1660,7 @@ Retry:
             '在退出同步锁后再进行日志输出
             Dim ErrOutput As New List(Of String)
             For Each Ex As Exception In ExList
-                ErrOutput.Add(GetExceptionDetail(Ex))
+                ErrOutput.Add(Ex.GetDetail())
             Next
             Log("[Download] " & Join(ErrOutput.Distinct.ToArray, vbCrLf))
         End Sub
@@ -1697,7 +1810,7 @@ Retry:
                         For Each File As NetFile In WaitingFiles
                             If NetTaskThreadCount >= NetTaskThreadLimit Then Continue While '最大线程数检查
                             Dim NewThread = File.TryBeginThread()
-                            If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(30) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次)
+                            If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(40) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次)
                         Next
                         '为进行中的文件追加线程
                         If Speed >= NetTaskSpeedLimitLow Then Continue While '下载速度足够,无需新增
@@ -1717,7 +1830,7 @@ Retry:
                             '新增线程
                             If PreparingCount > DownloadingCount Then Continue For '准备中的线程已多于下载中的线程,不再新增
                             Dim NewThread = File.TryBeginThread()
-                            If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(30) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次)
+                            If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(40) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次)
                         Next
                     End While
                 Catch ex As Exception
@@ -1842,4 +1955,15 @@ Retry:
         End If
     End Function
 
+    ''' <summary>
+    ''' 判断某个 Exception 是否为网络问题所导致。
+    ''' </summary>
+    <Extension> Public Function IsNetworkRelated(Ex As Exception) As Boolean
+        If TypeOf Ex Is DnsLookupException Then Return True
+        Dim Detail = Ex.GetDetail()
+        If Detail.Contains("(403)") Then Return False
+        Return {"(408)", "超时", "timeout", "网络请求失败", "连接尝试失败", "远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝"}.
+            Any(Function(k) Detail.ContainsF(k, True))
+    End Function
+
 End Module

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

@@ -191,7 +191,7 @@ Public Class ValidateExceptSame
         Me.ErrorMessage = ErrorMessage
         Me.IgnoreCase = IgnoreCase
     End Sub
-    Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
+    Public Sub New(Excepts As IEnumerable(Of String), Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
         Me.Excepts = New ObjectModel.Collection(Of String)
         For Each Data As String In Excepts
             Me.Excepts.Add(Data)
@@ -199,6 +199,11 @@ Public Class ValidateExceptSame
         Me.ErrorMessage = ErrorMessage
         Me.IgnoreCase = IgnoreCase
     End Sub
+    Public Sub New(Except As String, Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
+        Me.Excepts = New ObjectModel.Collection(Of String) From {Except}
+        Me.ErrorMessage = ErrorMessage
+        Me.IgnoreCase = IgnoreCase
+    End Sub
     Public Overrides Function Validate(Str As String) As String
         If Str Is Nothing Then Return ErrorMessage.Replace("%", "null")
         For Each Ch As String In Excepts

+ 7 - 8
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb

@@ -347,7 +347,7 @@
                             Case 4475 : Tags.Add("冒险")
                             Case 4476 : Tags.Add("探索")
                             Case 4477 : Tags.Add("小游戏")
-                            Case 4471 : Tags.Add("科幻")
+                            Case 4474 : Tags.Add("科幻")
                             Case 4736 : Tags.Add("空岛")
                             Case 5128 : Tags.Add("原版改良")
                             Case 4487 : Tags.Add("FTB")
@@ -1161,9 +1161,9 @@ Retry:
             ElseIf [Error] IsNot Nothing Then
                 '有结果但是有错误
                 If CurseForgeFailed Then
-                    Storage.ErrorMessage = $"无法连接到 CurseForge,所以目前仅显示了来自 Modrinth 的内容,搜索结果可能不全。{vbCrLf}请稍后重试,或使用 VPN 以改善网络环境。"
+                    Storage.ErrorMessage = $"无法连接到 CurseForge,所以目前仅显示了来自 Modrinth 的内容,搜索结果可能不全。{vbCrLf}请稍后再试,或使用 VPN 改善网络环境。"
                 Else
-                    Storage.ErrorMessage = $"无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,搜索结果可能不全。{vbCrLf}请稍后重试,或使用 VPN 以改善网络环境。"
+                    Storage.ErrorMessage = $"无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,搜索结果可能不全。{vbCrLf}请稍后再试,或使用 VPN 改善网络环境。"
                 End If
             End If
 
@@ -1354,7 +1354,7 @@ Retry:
         ''' </summary>
         ''' <param name="LocalAddress">目标本地文件夹,或完整的文件路径。会自动判断类型。</param>
         Public Function ToNetFile(LocalAddress As String) As NetFile
-            Return New NetFile(DownloadUrls, LocalAddress & If(LocalAddress.EndsWithF("\"), FileName, ""), New FileChecker(Hash:=Hash), UseBrowserUserAgent:=True)
+            Return New NetFile(DownloadUrls, LocalAddress & If(LocalAddress.EndsWithF("\"), FileName, ""), New FileChecker(Hash:=Hash), SimulateBrowserHeaders:=True)
         End Function
 
         '实例化
@@ -1395,10 +1395,9 @@ Retry:
                     If Hash Is Nothing Then Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 2)?("value")
                     'DownloadAddress
                     Dim Url = Data("downloadUrl").ToString
-                    If Url = "" Then Url = $"https://media.forgecdn.net/files/{CInt(Id.ToString.Substring(0, 4))}/{CInt(Id.ToString.Substring(4))}/{FileName}"
-                    Url = Url.Replace(FileName, WebUtility.UrlEncode(FileName)) '对文件名进行编码
-                    DownloadUrls = HandleCurseForgeDownloadUrls(Url) '对脑残 CurseForge 的下载地址进行多种修正
-                    DownloadUrls.AddRange(DownloadUrls.Select(Function(u) DlSourceModGet(u)).ToList) '添加镜像源,这个写法是为了让镜像源排在后面
+                    If Url = "" Then Url = $"https://edge.forgecdn.net/files/{CInt(Id.ToString.Substring(0, 4))}/{CInt(Id.ToString.Substring(4))}/{FileName}"
+                    DownloadUrls = HandleCurseForgeDownloadUrls(Url.Replace(FileName, WebUtility.UrlEncode(FileName))) '对脑残 CurseForge 的下载地址进行多种修正
+                    DownloadUrls.Add(DlSourceModGet(Url)) '添加镜像源;注意 MCIM 源不支持 URL 编码后的文件名,必须传入 URL 编码前的文件名
                     DownloadUrls = DownloadUrls.Distinct.ToList '最终去重
                     'Dependencies
                     If Data.ContainsKey("dependencies") Then

+ 15 - 11
Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb

@@ -485,7 +485,7 @@ Done:
         '崩溃报告分析,高优先级
         If LogCrash IsNot Nothing Then
             If LogCrash.Contains("Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass") Then AppendReason(CrashReason.Java版本过高)
-            If LogCrash.Contains("Failed loading config file ") Then AppendReason(CrashReason.Mod配置文件导致游戏崩溃, {TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Failed loading config file .+ for modid )[^\n]+"), "").TrimEnd(vbCrLf)).First, If(RegexSeek(LogCrash, "(?<=Failed loading config file ).+(?= of type)"), "").TrimEnd(vbCrLf)})
+            If LogCrash.Contains("Failed loading config file ") Then AppendReason(CrashReason.Mod配置文件导致游戏崩溃, {TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Failed loading config file .+ for modid )[^\n]+"), "").TrimEnd(vbCrLf)).First, If(RegexSeek(LogCrash, "(?<=Failed loading config file ).+(?= of type)"), "").TrimEnd(vbCrLf)}) '#7071
         End If
 
         '游戏日志分析
@@ -542,8 +542,8 @@ Done:
             If LogMc.Contains("Found a duplicate mod") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "Found a duplicate mod[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase))
             If LogMc.Contains("Found duplicate mods") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(LogMc, "(?<=Mod ID: ')\w+?(?=' from mod files:)").Distinct.ToList)
             If LogMc.Contains("ModResolutionException: Duplicate") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "ModResolutionException: Duplicate[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase))
-            If LogMc.Contains("Incompatible mods found!") Then '#5006
-                AppendReason(CrashReason.Mod互不兼容, If(RegexSeek(LogMc, "(?<=Incompatible mods found![\s\S]+: )[\s\S]+?(?=\tat )"), ""))
+            If LogMc.Contains("Incompatible mods found!") Then '#5006, #5115
+                AppendReason(CrashReason.Mod互不兼容, If(RegexSeek(LogMc, "(?<=Incompatible mods found![\s\S]+: )[\s\S]+?(?=\tat )"), "").BeforeFirst("更多信息:").Replace("Some of your mods are incompatible with the game or each other!", "").Trim((vbCrLf & " ").ToCharArray))
             End If
             If LogMc.Contains("Missing or unsupported mandatory dependencies:") Then
                 AppendReason(CrashReason.Mod缺少前置或MC版本错误,
@@ -581,6 +581,7 @@ Done:
             End If
             If LogCrash.Contains("Multiple entries with same key: ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Multiple entries with same key: )[^=]+"), "").TrimEnd((vbCrLf & " ").ToCharArray)))
             If LogCrash.Contains("LoaderExceptionModCrash: Caught exception from ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=LoaderExceptionModCrash: Caught exception from )[^\n]+"), "").TrimEnd((vbCrLf & " ").ToCharArray)))
+            If LogCrash.Contains("Failed loading config file ") Then AppendReason(CrashReason.Mod配置文件导致游戏崩溃, {TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Failed loading config file .+ for modid )[^\n]+"), "").TrimEnd(vbCrLf)).First, If(RegexSeek(LogCrash, "(?<=Failed loading config file ).+(?= of type)"), "").TrimEnd(vbCrLf)})
         End If
 
     End Sub
@@ -658,7 +659,7 @@ Done:
             End If
             'Mod 解析错误(常见于 Fabric 前置校验失败)
             If LogMc.Contains("Mod resolution failed") Then AppendReason(CrashReason.Mod加载器报错)
-            'Mixin 失败可以导致大量 Mod 实例创建失败
+            'Mixin 失败可以导致大量 Mod 实例创建失败,因此需要放到低优先级中
             If LogMc.Contains("Failed to create mod instance.") Then AppendReason(CrashReason.Mod初始化失败, TryAnalyzeModName(If(RegexSeek(LogMc, "(?<=Failed to create mod instance. ModID: )[^,]+"), If(RegexSeek(LogMc, "(?<=Failed to create mod instance. ModId )[^\n]+(?= for )"), "")).TrimEnd(vbCrLf)))
             '注意:Fabric 的 Warnings were found! 不一定是崩溃原因,它可能是单纯的警报
         End If
@@ -717,7 +718,7 @@ NextStack:
                     "preinit", "preload", "machine", "reflect", "channel", "general", "handler", "content", "systems", "modules", "service",
                     "fastutil", "optifine", "internal", "platform", "override", "fabricmc", "neoforge",
                     "injection", "listeners", "scheduler", "minecraft", "universal", "multipart", "neoforged", "microsoft",
-                    "transformer", "transformers", "minecraftforge", "blockentity", "spongepowered", "electronwill"
+                    "transformer", "transformers", "minecraftforge", "blockentity", "spongepowered", "electronwill", "concurrent"
                    }.Contains(Word.ToLower) Then Continue For
                 PossibleWords.Add(Word.Trim)
             Next
@@ -949,7 +950,7 @@ NextStack:
             Dim Additional As List(Of String) = Reason.Value
             Select Case Reason.Key
                 Case CrashReason.Java虚拟机参数有误
-                    Results.Add("由于 Java 虚拟机参数有误,导致游戏无法继续运行。\n请检查高级启动选项中设置的 Java 虚拟机参数是否有误。")
+                    Results.Add("由于 Java 参数有误,导致游戏无法继续运行。\n请检查高级选项中的 Java 虚拟机参数和游戏参数设置是否有误。")
                 Case CrashReason.Mod文件被解压
                     Results.Add("由于 Mod 文件被解压了,导致游戏无法继续运行。\n直接把整个 Mod 文件放进 Mod 文件夹中即可,若解压就会导致游戏出错。\n\n请删除 Mod 文件夹中已被解压的 Mod,然后再启动游戏。")
                 Case CrashReason.内存不足
@@ -1030,14 +1031,17 @@ NextStack:
                     End If
                 Case CrashReason.特定实体导致崩溃
                     If Additional.Count = 1 Then
-                        Dim entityInfo As String = Additional.First
-                        If entityInfo.StartsWith("minecraft:player") Then
-                            Results.Add("游戏似乎因为玩家实体 " & entityInfo & " 出现了问题。\n\n这通常是因为玩家数据损坏或世界存档问题导致的。\n请尝试以下解决方案:\n - 创建一个新世界,观察游戏是否正常运行\n - 如果新世界正常,可能需要删除或修复当前世界的玩家数据文件\n - 也可能是某个 Mod 与玩家实体处理存在冲突,尝试逐个禁用近期安装的 Mod\n - 如果问题持续存在,建议备份重要数据后重新创建世界\h")
+                        If Additional.First.Contains("minecraft:player") Then
+                            If Additional.First.Contains(" ") Then
+                                Results.Add($"游戏因为位于 {Additional.First.AfterFirst(" ")} 的玩家实体导致了崩溃。\h")
+                            Else
+                                Results.Add($"游戏因为玩家实体导致了崩溃。\h")
+                            End If
                         Else
-                            Results.Add("游戏似乎因为实体 " & entityInfo & " 出现了问题。\n\n请尝试以下解决方案:\n - 创建一个新世界,并尝试生成该类型的实体,观察游戏运行情况\n - 如果新世界中该实体正常,问题可能在于当前世界的数据损坏\n - 如果新世界中仍然崩溃,可能是某个 Mod 导致了实体处理异常\n - 尝试逐个禁用可能影响实体的 Mod(如生物类、AI 类 Mod)\n - 也可以尝试使用指令删除该坐标附近的实体\h")
+                            Results.Add($"游戏因为实体 {Additional.First} 导致了崩溃。\h")
                         End If
                     Else
-                        Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n请尝试以下解决方案:\n - 创建一个新世界,观察游戏的运行情况\n - 如果新世界正常运行,则是当前世界的实体数据导致出错\n - 可以尝试使用指令清理异常实体\n - 如果问题持续存在,可能是某个处理实体的 Mod 存在问题\n - 建议逐个禁用近期安装的 Mod,特别是涉及生物、AI 或实体处理的 Mod\h")
+                        Results.Add($"游戏因为下列实体导致了崩溃:{Additional.Join("、")}\h")
                     End If
                 Case CrashReason.OptiFine与Forge不兼容
                     Results.Add("由于 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n\n请前往 OptiFine 官网(https://optifine.net/downloads)查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏。")

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

@@ -198,7 +198,7 @@
     Private IsNewClientVersionHinted As Boolean = False
     Private Sub DlClientListMojangMain(Loader As LoaderTask(Of String, DlClientListResult))
         Dim StartTime As Long = GetTimeTick()
-        Dim Json As JObject = GetJson(NetRequestByClientRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json"))
+        Dim Json As JObject = GetJson(NetRequestByClientRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json", RequireJson:=True))
         Try
             Dim Versions As JArray = Json("versions")
             If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
@@ -235,7 +235,7 @@
     ''' </summary>
     Public DlClientListBmclapiLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Bmclapi", AddressOf DlClientListBmclapiMain)
     Private Sub DlClientListBmclapiMain(Loader As LoaderTask(Of String, DlClientListResult))
-        Dim Json As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json"))
+        Dim Json As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json", RequireJson:=True))
         Try
             Dim Versions As JArray = Json("versions")
             If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
@@ -413,7 +413,7 @@
     ''' </summary>
     Public DlOptiFineListBmclapiLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Bmclapi", AddressOf DlOptiFineListBmclapiMain)
     Private Sub DlOptiFineListBmclapiMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
-        Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/optifine/versionList"))
+        Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/optifine/versionList", RequireJson:=True))
         Try
             Dim Versions As New List(Of DlOptiFineListEntry)
             For Each Token As JObject In Json
@@ -483,7 +483,7 @@
     ''' </summary>
     Public DlForgeListOfficialLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Official", AddressOf DlForgeListOfficialMain)
     Private Sub DlForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
-        Dim Result As String = NetRequestByClientRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding:=Encoding.Default, Accept:="text/html", UseBrowserUserAgent:=True)
+        Dim Result As String = NetRequestByClientRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding:=Encoding.Default, Accept:="text/html", SimulateBrowserHeaders:=True)
         If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
         '获取所有版本信息
         Dim Names As List(Of String) = RegexSearch(Result, "(?<=a href=""index_)[0-9.]+(_pre[0-9]?)?(?=.html)")
@@ -631,9 +631,9 @@
         Try
             Result = NetRequestByLoader("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" &
                                           Loader.Input.Replace("-", "_") & '兼容 Forge 1.7.10-pre4,#4057
-                                          ".html", UseBrowserUserAgent:=True)
+                                          ".html", SimulateBrowserHeaders:=True)
         Catch ex As Exception
-            If GetExceptionSummary(ex).Contains("(404)") Then
+            If ex.GetBrief().Contains("(404)") Then
                 Throw New Exception("不可用")
             Else
                 Throw
@@ -701,7 +701,7 @@
     ''' </summary>
     Public Sub DlForgeVersionBmclapiMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
         Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" &
-                                                      Loader.Input.Replace("-", "_"))) '兼容 Forge 1.7.10-pre4,#4057
+                                                      Loader.Input.Replace("-", "_"), RequireJson:=True)) '兼容 Forge 1.7.10-pre4,#4057
         Dim Versions As New List(Of DlForgeVersionEntry)
         Try
             Dim Recommended As String = McDownloadForgeRecommendedGet(Loader.Input)
@@ -835,8 +835,8 @@
     Public DlNeoForgeListOfficialLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Official", AddressOf DlNeoForgeListOfficialMain)
     Private Sub DlNeoForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
         '获取版本列表 JSON
-        Dim ResultLatest As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge")
-        Dim ResultLegacy As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge")
+        Dim ResultLatest As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge", RequireJson:=True)
+        Dim ResultLegacy As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge", RequireJson:=True)
         If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
         '解析
         Try
@@ -853,8 +853,8 @@
     Public DlNeoForgeListBmclapiLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Bmclapi", AddressOf DlNeoForgeListBmclapiMain)
     Public Sub DlNeoForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
         '获取版本列表 JSON
-        Dim ResultLatest As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge")
-        Dim ResultLegacy As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge")
+        Dim ResultLatest As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge", RequireJson:=True)
+        Dim ResultLegacy As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge", RequireJson:=True)
         If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
         '解析
         Try
@@ -959,7 +959,7 @@
     ''' </summary>
     Public DlLiteLoaderListOfficialLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Official", AddressOf DlLiteLoaderListOfficialMain)
     Private Sub DlLiteLoaderListOfficialMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
-        Dim Result As JObject = GetJson(NetRequestByClientRetry("https://dl.liteloader.com/versions/versions.json"))
+        Dim Result As JObject = GetJson(NetRequestByClientRetry("https://dl.liteloader.com/versions/versions.json", RequireJson:=True))
         Try
             Dim Json As JObject = Result("versions")
             Dim Versions As New List(Of DlLiteLoaderListEntry)
@@ -987,7 +987,7 @@
     ''' </summary>
     Public DlLiteLoaderListBmclapiLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Bmclapi", AddressOf DlLiteLoaderListBmclapiMain)
     Private Sub DlLiteLoaderListBmclapiMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
-        Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json"))
+        Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json", RequireJson:=True))
         Try
             Dim Json As JObject = Result("versions")
             Dim Versions As New List(Of DlLiteLoaderListEntry)
@@ -1058,7 +1058,7 @@
     ''' </summary>
     Public DlFabricListOfficialLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Official", AddressOf DlFabricListOfficialMain)
     Private Sub DlFabricListOfficialMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
-        Dim Result As JObject = GetJson(NetRequestByClientRetry("https://meta.fabricmc.net/v2/versions"))
+        Dim Result As JObject = GetJson(NetRequestByClientRetry("https://meta.fabricmc.net/v2/versions", RequireJson:=True))
         Try
             Dim Output = New DlFabricListResult With {.IsOfficial = True, .SourceName = "Fabric 官方源", .Value = Result}
             If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
@@ -1073,7 +1073,7 @@
     ''' </summary>
     Public DlFabricListBmclapiLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Bmclapi", AddressOf DlFabricListBmclapiMain)
     Private Sub DlFabricListBmclapiMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
-        Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions"))
+        Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions", RequireJson:=True))
         Try
             Dim Output = New DlFabricListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Result}
             If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
@@ -1130,7 +1130,7 @@
         Dim Exs As String = ""
         For Each Source In Urls
             Try
-                Return GetJson(NetRequestByClient(Source.Key, Method, Content, ContentType, Timeout:=Source.Value * 1000, Encoding:=Encoding.UTF8))
+                Return GetJson(NetRequestByClient(Source.Key, Method, Content, ContentType, Timeout:=Source.Value * 1000, Encoding:=Encoding.UTF8, RequireJson:=True))
             Catch ex As Exception
                 Exs += ex.Message + vbCrLf
             End Try

+ 11 - 26
Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb

@@ -10,7 +10,7 @@
 
         '路径
         ''' <summary>
-        ''' Java.exe 文件的完整路径。
+        ''' java.exe 文件的完整路径。
         ''' </summary>
         Public ReadOnly Property PathJava As String
             Get
@@ -18,15 +18,7 @@
             End Get
         End Property
         ''' <summary>
-        ''' Javaw.exe 文件的完整路径。
-        ''' </summary>
-        Public ReadOnly Property PathJavaw As String
-            Get
-                Return PathFolder & "javaw.exe"
-            End Get
-        End Property
-        ''' <summary>
-        ''' Javaw.exe 文件所在文件夹的路径,以 \ 结尾。
+        ''' java.exe 文件所在文件夹的路径,以 \ 结尾。
         ''' </summary>
         Public PathFolder As String
         ''' <summary>
@@ -102,20 +94,13 @@
             Dim Output As String = Nothing
             Try
                 '确定文件存在
-                If Not File.Exists(PathJavaw) Then
-                    Throw New FileNotFoundException("未找到 javaw.exe 文件", PathJavaw)
-                End If
-                If Not File.Exists(PathFolder & "java.exe") Then
-                    Throw New FileNotFoundException("未找到 java.exe 文件", PathFolder & "java.exe")
-                End If
-                If File.Exists(PathFolder & "pdf-bookmark") Then
-                    Throw New Exception("不兼容 PDF Bookmark 的 Java") '#5326
-                End If
+                If Not File.Exists(PathJava) Then Throw New FileNotFoundException("未找到 java.exe 文件", PathJava)
+                If File.Exists(PathFolder & "pdf-bookmark") Then Throw New Exception("不兼容 PDF Bookmark 的 Java") '#5326
                 IsJre = Not File.Exists(PathFolder & "javac.exe")
                 '运行 -version
-                Output = ShellAndGetOutput(PathFolder & "java.exe", "-version", 15000).ToLower
+                Output = ShellAndGetOutput(PathJava, "-version", 15000).ToLower
                 If Output = "" Then Throw New ApplicationException("尝试运行该 Java 失败")
-                If ModeDebug Then Log("[Java] Java 检查输出:" & PathFolder & "java.exe" & vbCrLf & Output)
+                If ModeDebug Then Log("[Java] Java 检查输出:" & PathJava & vbCrLf & Output)
                 If Output.Contains("/lib/ext exists") Then Throw New ApplicationException("无法运行该 Java,请在删除 Java 文件夹中的 /lib/ext 文件夹后再试")
                 If Output.Contains("a fatal error") Then Throw New ApplicationException("无法运行该 Java,该 Java 或系统存在问题")
                 '获取详细信息
@@ -136,17 +121,17 @@
                 End If
                 Is64Bit = Output.Contains("64-bit")
                 If Version.Minor <= 4 OrElse Version.Minor >= 100 Then Throw New ApplicationException("分析详细信息失败,获取的版本为 " & Version.ToString)
-                '基于 #3649,在 64 位系统上禁用 32 位 Java
+                '#3649:在 64 位系统上禁用 32 位 Java
                 If Not Is64Bit AndAlso Not Is32BitSystem Then Throw New Exception("该 Java 为 32 位版本,请安装 64 位的 Java")
-                '基于 #2249 发现 JRE 17 似乎也导致了 Forge 安装失败,干脆禁用更多版本的 JRE
+                '#2249:JRE 17 似乎会导致 Forge 安装失败,干脆禁用更多版本的 JRE
                 If IsJre AndAlso VersionCode >= 16 Then Throw New Exception("由于高版本 JRE 对游戏的兼容性很差,因此不再允许使用。你可以使用对应版本的 JDK,而非 JRE!")
             Catch ex As ApplicationException
                 Throw ex
             Catch ex As ThreadInterruptedException
                 Throw ex
             Catch ex As Exception
-                Log("[Java] 检查失败的 Java 输出:" & PathFolder & "java.exe" & vbCrLf & If(Output, "无程序输出"))
-                Throw New Exception("检查 Java 失败(" & If(PathJavaw, "Nothing") & ")", ex)
+                Log("[Java] 检查失败的 Java 输出:" & If(PathJava, "Nothing") & vbCrLf & If(Output, "无程序输出"))
+                Throw New Exception("检查 Java 失败(" & If(PathJava, "Nothing") & ")", ex)
             End Try
             IsChecked = True
         End Sub
@@ -689,7 +674,7 @@ Wait:
                             "应用", "运行", "前置", "mojang", "官启", "新建文件夹", "eclipse", "microsoft", "hotspot",
                             "runtime", "x86", "x64", "forge", "原版", "optifine", "官方", "启动", "hmcl", "mod", "高清",
                             "download", "launch", "程序", "path", "version", "baka", "pcl", "zulu", "local", "packages",
-                            "4297127d64ec6", "1.", "启动", "jbr-"}
+                            "4297127d64ec6", "1.", "启动", "jbr"}
             For Each FolderInfo As DirectoryInfo In OriginalPath.EnumerateDirectories
                 If FolderInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) Then Continue For '跳过符号链接
                 Dim SearchEntry = GetFolderNameFromPath(FolderInfo.Name).ToLower '用于搜索的字符串

+ 45 - 52
Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb

@@ -178,7 +178,7 @@ NextInner:
                 GoTo NextInner
             Else
                 '没有特殊处理过的错误信息
-                McLaunchLog("错误:" & GetExceptionDetail(ex))
+                McLaunchLog("错误:" & ex.GetDetail())
                 Log(ex,
                     If(IsSavingBatch, "导出启动脚本失败", "Minecraft 启动失败"), LogLevel.Msgbox,
                     If(IsSavingBatch, "导出启动脚本失败", "启动失败"))
@@ -187,7 +187,7 @@ NextInner:
                     Telemetry("Minecraft 启动失败",
                               "Version", McVersionCurrent.Version.ToString,
                               "LoginType", GetStringFromEnum(CType(Setup.Get("LoginType"), McLoginType)),
-                              "Exception", FilterUserName(FilterAccessToken(GetExceptionDetail(ex), "*"), "*"))
+                              "Exception", FilterUserName(FilterAccessToken(ex.GetDetail(), "*"), "*"))
                 End If
                 Throw
             End If
@@ -609,12 +609,8 @@ SkipLogin:
                 McLoginRequestValidate(Data)
                 GoTo LoginFinish
             Catch ex As Exception
-                Dim AllMessage = GetExceptionDetail(ex)
-                McLaunchLog("验证登录失败:" & AllMessage)
-                If (AllMessage.Contains("超时") OrElse AllMessage.Contains("imeout")) AndAlso Not AllMessage.Contains("403") Then
-                    McLaunchLog("已触发超时登录失败")
-                    Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后重试,或使用加速器或 VPN 以改善网络环境。")
-                End If
+                Log(ex, "验证登录失败")
+                If IsNetworkRelated(ex) Then Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后再试,或使用加速器、VPN 改善网络环境。")
             End Try
             Data.Progress = 0.25
             '尝试刷新登录
@@ -624,7 +620,7 @@ Refresh:
                 McLoginRequestRefresh(Data, NeedRefresh)
                 GoTo LoginFinish
             Catch ex As Exception
-                McLaunchLog("刷新登录失败:" & GetExceptionDetail(ex))
+                McLaunchLog("刷新登录失败:" & ex.GetDetail())
                 If WasRefreshed Then Throw New Exception("二轮刷新登录失败", ex)
             End Try
             Data.Progress = If(NeedRefresh, 0.85, 0.45)
@@ -634,7 +630,7 @@ Refresh:
             If Data.IsAborted Then Throw New ThreadInterruptedException
             NeedRefresh = McLoginRequestLogin(Data)
         Catch ex As Exception
-            McLaunchLog("登录失败:" & GetExceptionDetail(ex))
+            McLaunchLog("登录失败:" & ex.GetDetail())
             Throw
         End Try
         If NeedRefresh Then
@@ -699,7 +695,7 @@ LoginFinish:
         '发送登录请求
         Dim RequestData As New JObject(
             New JProperty("accessToken", AccessToken), New JProperty("clientToken", ClientToken), New JProperty("requestUser", True))
-        NetRequestByClientRetry(
+        NetRequestByClient(
             Data.Input.BaseUrl & "/validate", HttpMethod.Post,
             Content:=RequestData.ToString(Newtonsoft.Json.Formatting.None),
             Headers:={{"Accept-Language", "zh-CN"}},
@@ -726,7 +722,7 @@ LoginFinish:
                    New JProperty("requestUser", True)
                ).ToString(Newtonsoft.Json.Formatting.None),
                Headers:={{"Accept-Language", "zh-CN"}},
-               ContentType:="application/json; charset=utf-8"))
+               ContentType:="application/json; charset=utf-8", RequireJson:=True))
         '将登录结果输出
         If LoginJson("selectedProfile") Is Nothing Then Throw New Exception("选择的角色 " & Setup.Get("Cache" & Data.Input.Token & "Name") & " 无效!")
         Data.Output.AccessToken = LoginJson("accessToken").ToString
@@ -756,7 +752,7 @@ LoginFinish:
                 Data.Input.BaseUrl & "/authenticate", HttpMethod.Post,
                 Content:=RequestData.ToString(Newtonsoft.Json.Formatting.None),
                 Headers:={{"Accept-Language", "zh-CN"}},
-                ContentType:="application/json; charset=utf-8"))
+                ContentType:="application/json; charset=utf-8", RequireJson:=True))
             '检查登录结果
             If LoginJson("availableProfiles").Count = 0 Then
                 If Data.Input.ForceReselectProfile Then Hint("你还没有创建角色,无法更换!", HintType.Critical)
@@ -814,13 +810,14 @@ LoginFinish:
             McLaunchLog("登录成功(Login, " & Data.Input.Token & ")")
             Return NeedRefresh
         Catch ex As Exception
-            Dim AllMessage As String = GetExceptionSummary(ex)
+            Dim AllMessage As String = ex.GetBrief()
             Log(ex, "登录失败原始错误信息", LogLevel.Normal)
             '读取服务器返回的错误
             If TypeOf ex Is ResponsedWebException Then
                 Dim ErrorMessage As String = Nothing
                 Try
-                    ErrorMessage = GetJson(DirectCast(ex, ResponsedWebException).Response)("errorMessage")
+                    Dim Response = DirectCast(ex, ResponsedWebException).Response
+                    If ErrorMessage.ContainsF("errorMessage", True) Then ErrorMessage = GetJson(Response)("errorMessage")
                 Catch
                 End Try
                 If Not String.IsNullOrWhiteSpace(ErrorMessage) Then
@@ -838,7 +835,7 @@ LoginFinish:
                 End If
             End If
             '通用关键字检测
-            If AllMessage.Contains("403") Then
+            If AllMessage.Contains("(403)") Then
                 Select Case Data.Input.Type
                     Case McLoginType.Auth
                         Throw New Exception("$登录失败,以下为可能的原因:" & vbCrLf &
@@ -852,8 +849,8 @@ LoginFinish:
                                             If(Data.Input.UserName.Contains("@"), "", " - 登录账号应为邮箱或统一通行证账号,而非游戏角色 ID。" & vbCrLf) &
                                             " - 只注册了账号,但没有加入对应服务器。")
                 End Select
-            ElseIf AllMessage.Contains("超时") OrElse AllMessage.Contains("imeout") OrElse AllMessage.Contains("网络请求失败") OrElse AllMessage.Contains("408") OrElse AllMessage.Contains("由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。") Then
-                Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后重试,或使用加速器或 VPN 以改善网络环境。")
+            ElseIf ex.IsNetworkRelated() Then
+                Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后再试,或使用加速器、VPN 改善网络环境。")
             ElseIf ex.Message.StartsWithF("$") Then
                 Throw
             Else
@@ -872,7 +869,7 @@ Retry:
         McLaunchLog("开始微软登录步骤 1/6(原始登录)")
         Dim PrepareJson As JObject = GetJson(NetRequestByClientRetry("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", HttpMethod.Post,
             Content:=$"client_id={OAuthClientId}&tenant=/consumers&scope=XboxLive.signin%20offline_access",
-            ContentType:="application/x-www-form-urlencoded"))
+            ContentType:="application/x-www-form-urlencoded", RequireJson:=True))
         McLaunchLog("网页登录地址:" & PrepareJson("verification_uri").ToString)
 
         '弹窗
@@ -907,6 +904,7 @@ Retry:
                 Headers:={{"Accept-Language", "en-US,en;q=0.5"}, {"X-Requested-With", "XMLHttpRequest"}},
                 ThreadCount:=2)
         Catch ex As ResponsedWebException
+            '修改错误列表时,同时检查 MyMsgLogin.xaml.vb 中的对应代码
             Dim Response = ex.Response
             If Response.ContainsF("must sign in again", True) OrElse Response.ContainsF("password expired", True) OrElse
                (Response.Contains("refresh_token") AndAlso Response.Contains("is not valid")) Then '#269
@@ -941,7 +939,7 @@ Retry:
            ""TokenType"": ""JWT""
         }"
         Dim Result As String = NetRequestByClientMultiple("https://user.auth.xboxlive.com/user/authenticate", HttpMethod.Post,
-            Content:=Request, ContentType:="application/json")
+            Content:=Request, ContentType:="application/json", RequireJson:=True)
         Dim ResultJson As JObject = GetJson(Result)
         Dim XBLToken As String = ResultJson("Token").ToString
         Return XBLToken
@@ -963,7 +961,7 @@ Retry:
         Dim Result As String
         Try
             Result = NetRequestByClientMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", HttpMethod.Post,
-                Content:=Request, ContentType:="application/json")
+                Content:=Request, ContentType:="application/json", RequireJson:=True)
         Catch ex As ResponsedWebException
             '参考 https://github.com/PrismarineJS/prismarine-auth/blob/master/src/common/Constants.js
             If ex.Response.Contains("2148916227") Then
@@ -975,7 +973,7 @@ Retry:
                 End If
                 Throw New Exception("$$")
             ElseIf ex.Response.Contains("2148916235") Then
-                MyMsgBox($"你的网络所在的国家或地区无法登录微软账号。{vbCrLf}请使用加速器或 VPN。", "登录失败", "我知道了")
+                MyMsgBox($"你的网络所在的国家或地区无法登录微软账号。{vbCrLf}请使用加速器或 VPN,然后再试。", "登录失败", "我知道了")
                 Throw New Exception("$$")
             ElseIf ex.Response.Contains("2148916238") Then
                 If MyMsgBox("该账号年龄不足,你需要先修改出生日期,然后才能登录。" & vbCrLf &
@@ -994,15 +992,11 @@ Retry:
             End If
         End Try
 
-        Try '这个 Try 块仅为了追踪 #6683 而添加
-            Dim ResultJson As JObject = GetJson(Result)
-            Dim XSTSToken As String = ResultJson("Token").ToString
-            Dim UHS As String = ResultJson("DisplayClaims")("xui")(0)("uhs").ToString
-            Return {XSTSToken, UHS}
-        Catch
-            Log("[Launch] 返回内容:" & vbCrLf & Result)
-            Throw
-        End Try
+        Dim ResultJson As JObject = GetJson(Result)
+        Return {
+            ResultJson("Token"), 'XSTSToken
+            ResultJson("DisplayClaims")("xui")(0)("uhs") 'UHS
+        }
     End Function
     '微软登录步骤 4:从 {XSTSToken, UHS} 获取 Minecraft AccessToken
     Private Function MsLoginStep4(Tokens As String()) As String
@@ -1012,9 +1006,9 @@ Retry:
         Dim Result As String
         Try
             Result = NetRequestByClientRetry("https://api.minecraftservices.com/authentication/login_with_xbox", HttpMethod.Post,
-                Content:=Request, ContentType:="application/json")
+                Content:=Request, ContentType:="application/json", RequireJson:=True)
         Catch ex As Net.WebException
-            Dim Message As String = GetExceptionSummary(ex)
+            Dim Message As String = ex.GetBrief()
             If Message.Contains("(429)") Then
                 Log(ex, "微软登录第 4 步汇报 429")
                 Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!")
@@ -1026,16 +1020,14 @@ Retry:
             End If
         End Try
 
-        Dim ResultJson As JObject = GetJson(Result)
-        Dim AccessToken As String = ResultJson("access_token").ToString
-        Return AccessToken
+        Return GetJson(Result)("access_token").ToString
     End Function
     '微软登录步骤 5:验证微软账号是否持有 MC,这也会刷新 XGP
     Private Sub MsLoginStep5(AccessToken As String)
         McLaunchLog("开始微软登录步骤 5/6")
 
         Dim Result As String = NetRequestByClientMultiple("https://api.minecraftservices.com/entitlements/mcstore",
-            ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}})
+            ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}}, RequireJson:=True)
         Try
             Dim ResultJson As JObject = GetJson(Result)
             If Not (ResultJson.ContainsKey("items") AndAlso ResultJson("items").Any) Then
@@ -1057,9 +1049,9 @@ Retry:
         Dim Result As String
         Try
             Result = NetRequestByClientMultiple("https://api.minecraftservices.com/minecraft/profile",
-                ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}})
-        Catch ex As Net.WebException
-            Dim Message As String = GetExceptionSummary(ex)
+                ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}}, RequireJson:=True)
+        Catch ex As WebException
+            Dim Message As String = ex.GetBrief()
             If Message.Contains("(429)") Then
                 Log(ex, "微软登录第 6 步汇报 429")
                 Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!")
@@ -1130,7 +1122,7 @@ Retry:
         If Len(Uuid) = 32 Then Return Uuid
         '从官网获取
         Try
-            Dim GotJson As JObject = GetJson(NetRequestByClientRetry("https://api.mojang.com/users/profiles/minecraft/" & Name))
+            Dim GotJson As JObject = GetJson(NetRequestByClientRetry("https://api.mojang.com/users/profiles/minecraft/" & Name, RequireJson:=True))
             If GotJson Is Nothing Then Throw New FileNotFoundException("正版玩家档案不存在(" & Name & ")")
             Uuid = If(GotJson("id"), "")
         Catch ex As Exception
@@ -1495,6 +1487,9 @@ Retry:
             DataList.Add(Version.JsonObject("mainClass"))
         End If
 
+        '处理 DataList 的换行符
+        DataList = DataList.Select(Function(d) d.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, " ").Trim).ToList
+
         Return Join(DataList, " ")
     End Function
     Private Function McLaunchArgumentsJvmNew(Version As McVersion) As String
@@ -1538,9 +1533,7 @@ NextVersion:
         'Authlib-Injector
         If McLoginLoader.Output.Type = "Auth" Then
             If McLaunchJavaSelected.VersionCode >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#)
-            Dim Server As String = If(McLoginLoader.Input.Type = McLoginType.Legacy,
-                "http://hiperauth.tech/api/yggdrasil-hiper/", 'HiPer 登录
-                Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent))
+            Dim Server As String = Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent)
             Try
                 Dim Response As String = NetRequestByClientRetry(Server, Encoding:=Encoding.UTF8)
                 DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server &
@@ -1562,7 +1555,7 @@ NextVersion:
         '如果不合并,会导致 Forge 1.17 启动无效,它有两个 --add-exports,进一步导致其中一个在后面被去重
         Dim DeDuplicateDataList As New List(Of String)
         For i = 0 To DataList.Count - 1
-            Dim CurrentEntry As String = DataList(i)
+            Dim CurrentEntry As String = DataList(i).Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, " ").Trim
             If DataList(i).StartsWithF("-") Then
                 Do While i < DataList.Count - 1
                     If DataList(i + 1).StartsWithF("-") Then
@@ -1803,7 +1796,7 @@ NextVersion:
                             File.Delete(FilePath)
                         Catch ex As UnauthorizedAccessException
                             McLaunchLog("删除原 dll 访问被拒绝,这通常代表有一个 MC 正在运行,跳过解压:" & FilePath)
-                            McLaunchLog("实际的错误信息:" & GetExceptionSummary(ex))
+                            McLaunchLog("实际的错误信息:" & ex.GetDetail())
                             Exit For
                         End Try
                     End If
@@ -1823,7 +1816,7 @@ NextVersion:
                 File.Delete(FileName)
             Catch ex As UnauthorizedAccessException
                 McLaunchLog("删除多余文件访问被拒绝,跳过删除步骤")
-                McLaunchLog("实际的错误信息:" & GetExceptionSummary(ex))
+                McLaunchLog("实际的错误信息:" & ex.GetDetail())
                 Return
             End Try
         Next
@@ -1849,7 +1842,7 @@ NextVersion:
         '要求 Java 使用高性能显卡
         If Setup.Get("LaunchAdvanceGraphicCard") Then
             Try
-                SetGPUPreference(McLaunchJavaSelected.PathJavaw)
+                SetGPUPreference(McLaunchJavaSelected.PathJava)
                 SetGPUPreference(PathWithName)
             Catch ex As Exception
                 If IsAdmin() Then
@@ -1857,7 +1850,7 @@ NextVersion:
                 Else
                     Log(ex, "直接调整显卡设置失败,将以管理员权限重启 PCL 再次尝试")
                     Try
-                        If RunAsAdmin($"--gpu ""{McLaunchJavaSelected.PathJavaw}""") = ProcessReturnValues.TaskDone Then
+                        If RunAsAdmin($"--gpu ""{McLaunchJavaSelected.PathJava}""") = ProcessReturnValues.TaskDone Then
                             McLaunchLog("以管理员权限重启 PCL 并调整显卡设置成功")
                         Else
                             Throw New Exception("调整过程中出现异常")
@@ -2218,7 +2211,7 @@ IgnoreCustomSkin:
 
         '启动信息
         Dim GameProcess = New Process()
-        Dim StartInfo As New ProcessStartInfo(McLaunchJavaSelected.PathJavaw)
+        Dim StartInfo As New ProcessStartInfo(McLaunchJavaSelected.PathJava) '使用 javaw.exe 会导致 #6263
 
         '设置环境变量
         Dim Paths As New List(Of String)(StartInfo.EnvironmentVariables("Path").Split(";"))
@@ -2231,13 +2224,13 @@ IgnoreCustomSkin:
         StartInfo.UseShellExecute = False
         StartInfo.RedirectStandardOutput = True
         StartInfo.RedirectStandardError = True
-        StartInfo.CreateNoWindow = False
+        StartInfo.CreateNoWindow = True
         StartInfo.Arguments = McLaunchArgument
         GameProcess.StartInfo = StartInfo
 
         '开始进程
         GameProcess.Start()
-        McLaunchLog("已启动游戏进程:" & McLaunchJavaSelected.PathJavaw)
+        McLaunchLog("已启动游戏进程:" & McLaunchJavaSelected.PathJava)
         If Loader.IsAborted Then
             McLaunchLog("由于取消启动,已强制结束游戏进程") '#1631
             GameProcess.Kill()

+ 25 - 15
Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb

@@ -86,7 +86,7 @@ Public Module ModMinecraft
                     Next
                     If Not Renamed Then CacheMcFolderList.Add(New McFolder With {.Name = Name, .Path = Path, .Type = McFolderType.Custom})
                 Catch ex As Exception
-                    MyMsgBox("失效的 Minecraft 文件夹:" & vbCrLf & Path & vbCrLf & vbCrLf & GetExceptionSummary(ex), "Minecraft 文件夹失效", IsWarn:=True)
+                    MyMsgBox("失效的 Minecraft 文件夹:" & vbCrLf & Path & vbCrLf & vbCrLf & ex.GetBrief(), "Minecraft 文件夹失效", IsWarn:=True)
                     Log(ex, $"无法访问 Minecraft 文件夹 {Path}")
                 End Try
             Next
@@ -695,7 +695,7 @@ Recheck:
             Catch ex As Exception
                 Log(ex, "依赖版本检查出错(" & Name & ")")
                 State = McVersionState.Error
-                Info = "未知错误:" & GetExceptionSummary(ex)
+                Info = "未知错误:" & ex.GetBrief()
                 Return False
             End Try
 
@@ -808,7 +808,7 @@ ExitDataLoad:
                     WriteIni(Path & "PCL\Setup.ini", "VersionOriginalSub", Version.McCodeSub)
                 End If
             Catch ex As Exception
-                Info = "未知错误:" & GetExceptionSummary(ex)
+                Info = "未知错误:" & ex.GetBrief()
                 Logo = PathImage & "Blocks/RedstoneBlock.png"
                 State = McVersionState.Error
                 Log(ex, "加载版本失败(" & Name & ")", LogLevel.Feedback)
@@ -1186,6 +1186,7 @@ OnLoaded:
             If Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(200, 3000))
         Catch ex As ThreadInterruptedException
         Catch ex As Exception
+            If Loader.IsAborted Then Return '#5617
             WriteIni(Path & "PCL.ini", "VersionCache", "") '要求下次重新加载
             Log(ex, "加载 .minecraft 版本列表失败", LogLevel.Feedback)
         End Try
@@ -1592,11 +1593,11 @@ OnLoaded:
     ''' <summary>
     ''' 获取 Uuid 对应的皮肤文件地址,失败将抛出异常。
     ''' </summary>
-    Public Function McSkinGetAddress(Uuid As String, Type As String) As String
-        If Uuid = "" Then Throw New Exception("Uuid 为空。")
-        If Uuid.StartsWithF("00000") Then Throw New Exception("离线 Uuid 无正版皮肤文件。")
+    Public Function McSkinGetAddress(UUID As String, Type As String) As String
+        If UUID = "" Then Throw New Exception("UUID 为空。")
+        If UUID.StartsWithF("00000") AndAlso Type <> "Auth" Then Throw New Exception("离线 UUID 无正版皮肤文件:" & UUID)
         '尝试读取缓存
-        Dim CacheSkinAddress As String = ReadIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", Uuid)
+        Dim CacheSkinAddress As String = ReadIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", UUID)
         If Not CacheSkinAddress = "" Then Return CacheSkinAddress
         '获取皮肤地址
         Dim Url As String
@@ -1610,7 +1611,7 @@ OnLoaded:
             Case Else
                 Throw New ArgumentException("皮肤地址种类无效:" & If(Type, "null"))
         End Select
-        Dim SkinString = NetRequestByClientRetry(Url & Uuid)
+        Dim SkinString = NetRequestByClientRetry(Url & UUID, RequireJson:=True)
         If SkinString = "" Then Throw New Exception("皮肤返回值为空,可能是未设置自定义皮肤的用户")
         '处理皮肤地址
         Dim SkinValue As String
@@ -1635,8 +1636,8 @@ OnLoaded:
             SkinValue = If(SkinUrl.Contains("minecraft.net/"), SkinUrl.Replace("http://", "https://"), SkinUrl)
         End If
         '保存缓存
-        WriteIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", Uuid, SkinValue)
-        Log("[Skin] UUID " & Uuid & " 对应的皮肤文件为 " & SkinValue)
+        WriteIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", UUID, SkinValue)
+        Log("[Skin] UUID " & UUID & " 对应的皮肤文件为 " & SkinValue)
         Return SkinValue
     End Function
 
@@ -1966,7 +1967,7 @@ OnLoaded:
             Try
                 Log("[Minecraft] 开始获取统一通行证下载信息")
                 '测试链接:https://auth.mc-user.com:233/00000000000000000000000000000000/
-                DownloadInfo = GetJson(NetRequestByClientRetry("https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version)))
+                DownloadInfo = GetJson(NetRequestByClientRetry("https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version), RequireJson:=True))
             Catch ex As Exception
                 Log(ex, "获取统一通行证下载信息失败")
             End Try
@@ -1983,16 +1984,20 @@ OnLoaded:
 
         'Authlib-Injector 文件
         If Setup.Get("VersionServerLogin", Version:=Version) = 4 Then
-            Dim TargetFile = PathPure & "\authlib-injector.jar"
+            Dim TargetFile = PathPure & "authlib-injector.jar"
             Dim DownloadInfo As JObject = Nothing
             '获取下载信息
             Try
                 Log("[Minecraft] 开始获取 Authlib-Injector 下载信息")
                 DownloadInfo = GetJson(NetRequestByClientRetry(
                         "https://authlib-injector.yushi.moe/artifact/latest.json",
-                        BackupUrl:="https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json"))
+                        BackupUrl:="https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json", RequireJson:=True))
             Catch ex As Exception
-                Log(ex, "获取 Authlib-Injector 下载信息失败")
+                If File.Exists(TargetFile) Then
+                    Log(ex, "获取 Authlib-Injector 下载信息失败")
+                Else
+                    Throw New Exception("获取 Authlib-Injector 下载信息失败", ex)
+                End If
             End Try
             '校验文件
             If DownloadInfo IsNot Nothing Then
@@ -2415,7 +2420,12 @@ NextEntry:
     Public Function FilterUserName(Raw As String, FilterChar As Char) As String
         If Raw.Contains(":\Users\") Then
             For Each Token In RegexSearch(Raw, "(?<=:\\Users\\)[^\\]+")
-                Raw = Raw.Replace("\" & Token, "\" & New String(FilterChar, Token.Count))
+                Raw = Raw.Replace("\Users\" & Token, "\Users\" & New String(FilterChar, Token.Count))
+            Next
+        End If
+        If Raw.Contains(":/Users/") Then
+            For Each Token In RegexSearch(Raw, "(?<=:/Users/)[^/]+")
+                Raw = Raw.Replace("/Users/" & Token, "/Users/" & New String(FilterChar, Token.Count))
             Next
         End If
         Return Raw

+ 9 - 8
Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb

@@ -1113,8 +1113,8 @@ Finished:
         End Sub, "Mod List Detail Loader CurseForge")
         '等待线程结束
         Do Until EndedThreadCount = 2
-            If Loader.IsAborted Then Return
             Thread.Sleep(10)
+            If Loader.IsAborted Then Return
         Loop
         '保存缓存
         Mods = Mods.Where(Function(m) m.Comp IsNot Nothing).ToList()
@@ -1126,6 +1126,7 @@ Finished:
         Next
         WriteFile(PathTemp & "Cache\LocalMod.json", Cache.ToString(If(ModeDebug, Newtonsoft.Json.Formatting.Indented, Newtonsoft.Json.Formatting.None)))
         '刷新边栏
+        If Loader.IsAborted Then Return
         If FrmVersionMod?.Filter = PageVersionMod.FilterType.CanUpdate Then
             RunInUi(Sub() FrmVersionMod?.RefreshUI()) '同步 “可更新” 列表 (#4677)
         Else
@@ -1212,7 +1213,7 @@ Finished:
                         If ReqVersionHead = ReqVersionTail Then
                             VersionRequire = "应为 " & ReqVersionHead
                         ElseIf ReqVersionHead.Contains(".") AndAlso ReqVersionTail.Contains(".") Then
-                            VersionRequire = "应为 " & ReqVersionHead & " 至 " & ReqVersionTail
+                            VersionRequire = $"应为 {ReqVersionHead} 至 {ReqVersionTail}"
                         ElseIf ReqVersionHead.Contains(".") Then
                             If ReqVersionHeadCanEqual Then
                                 VersionRequire = "最低应为 " & ReqVersionHead
@@ -1230,7 +1231,7 @@ Finished:
                         End If
                         '检查前置 Mod 是否存在,并获取其版本
                         If Not CurrentDependencies.ContainsKey(ReqId) Then
-                            Result.Add("缺少前置 Mod:" & ReqId & If(VersionRequire = "", "", ",其版本" & VersionRequire) & "。" & vbCrLf & " - " & ModEntity.FileName)
+                            Result.Add($"缺少前置 Mod:{ReqId}{If(VersionRequire = "", "", ",其版本" & VersionRequire)}。{vbCrLf} - {ModEntity.FileName}")
                             Continue For
                         End If
                         Dim CurrentVersion As String = If(CurrentDependencies(ReqId)(0), "0.0")
@@ -1239,7 +1240,7 @@ Finished:
                         If ReqVersionHead.Contains(".") Then
                             If VersionSortInteger(ReqVersionHead, CurrentVersion) > If(ReqVersionHeadCanEqual, 0, -1) Then
                                 Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过低,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf &
-                                           " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", vbCrLf & " - 前置:" & CurrentDependencies(ReqId)(1), ""))
+                                           " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", $"{vbCrLf} - 前置:{CurrentDependencies(ReqId)(1)}", ""))
                                 Continue For
                             End If
                         End If
@@ -1247,26 +1248,26 @@ Finished:
                         If ReqVersionTail.Contains(".") Then
                             If VersionSortInteger(CurrentVersion, ReqVersionTail) > If(ReqVersionTailCanEqual, 0, -1) Then
                                 Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过高,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf &
-                                           " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", vbCrLf & " - 前置:" & CurrentDependencies(ReqId)(1), ""))
+                                           " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", $"{vbCrLf} - 前置:{CurrentDependencies(ReqId)(1)}", ""))
                                 Continue For
                             End If
                         End If
                     Else
                         If Not CurrentDependencies.ContainsKey(Dependency.Key) Then
-                            Result.Add("缺少前置 Mod:" & Dependency.Key & "。" & vbCrLf & " - " & ModEntity.FileName)
+                            Result.Add($"缺少前置 Mod:{Dependency.Key}。{vbCrLf} - {ModEntity.FileName}")
                             Continue For
                         End If
                     End If
                 Next
             Catch ex As Exception
-                Result.Add("检查 Mod 时出错:" & GetExceptionSummary(ex) & vbCrLf & " - " & ModEntity.FileName)
+                Result.Add($"检查 Mod 时出错:{ex.GetBrief()}{vbCrLf} - {ModEntity.FileName}")
                 Log(ex, "检查 Mod 时出错")
             End Try
         Next
         If Not Result.Any() Then
             Log("[Minecraft] Mod 检查未发现异常")
         Else
-            Log("[Minecraft] Mod 检查异常结果:" & vbCrLf & Join(Result, vbCrLf))
+            Log($"[Minecraft] Mod 检查异常结果:{vbCrLf}{Join(Result, vbCrLf)}")
         End If
         Return Result
     End Function

+ 19 - 11
Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb

@@ -72,7 +72,7 @@ Public Module ModModpack
                     If FullNames(1) = "modpack.zip" OrElse FullNames(1) = "modpack.mrpack" Then PackType = 9 : Exit Try '带启动器的压缩包
                 Next
             Catch ex As Exception
-                If GetExceptionDetail(ex, True).Contains("Error.WinIOError") Then
+                If ex.GetDetail(True).Contains("Error.WinIOError") Then
                     Throw New Exception("打开整合包文件失败", ex)
                 ElseIf File.EndsWithF(".rar", True) Then
                     Throw New Exception("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试", ex)
@@ -166,6 +166,15 @@ Retry:
         End If
         IniClearCache(VersionIni) '重置缓存,避免被安装过程中写入的 ini 覆盖
     End Sub
+    ''' <summary>
+    ''' 弹窗提示整合包中存在不兼容的加载器。
+    ''' 如果取消,则抛出 CancelledException。
+    ''' </summary>
+    ''' <exception cref="CancelledException" />
+    Private Sub NotifyIncompatibleLoader(LoaderName As String)
+        If MyMsgBox($"整合包中存在不兼容的加载器:{LoaderName}{vbCrLf}如果你知道如何手动安装它,可以先选择跳过,然后在整合包下载结束后手动安装。",
+            "不兼容的加载器", "取消", $"不安装 {LoaderName} 并继续") = 1 Then Throw New CancelledException
+    End Sub
 
 #Region "不同类型整合包的安装方法"
 
@@ -211,7 +220,8 @@ Retry:
                 Log("[ModPack] 整合包 Fabric 版本:" & Id)
                 FabricVersion = Id.Replace("fabric-", "")
             Else
-                Log("[ModPack] 未知 Mod 加载器:" & Id)
+                'ElseIf Id.StartsWithF("quilt-") Then
+                NotifyIncompatibleLoader(Id)
             End If
         Next
         '解压
@@ -376,12 +386,9 @@ Retry:
                 Case "fabric-loader" 'eg. 0.14.14
                     FabricVersion = Entry.Value.ToString
                     Log("[ModPack] 整合包 Fabric 版本:" & FabricVersion)
-                Case "quilt-loader" 'eg. 1.0.0
-                    Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical)
-                    Throw New CancelledException
                 Case Else
-                    Hint($"无法安装整合包,其中出现了未知的 Mod 加载器 {Entry.Value}!", HintType.Critical)
-                    Throw New CancelledException
+                    'Case "quilt-loader" 'eg. 1.0.0
+                    NotifyIncompatibleLoader(Entry.Name)
             End Select
         Next
         '获取版本名
@@ -633,7 +640,8 @@ Retry:
         If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!")
         Dim Request As New McInstallRequest With {.TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\"}
         For Each Component In PackJson("components")
-            Select Case If(Component("uid"), "").ToString
+            If Not Component.Contains("uid") Then Continue For
+            Select Case Component("uid").ToString
                 Case "org.lwjgl"
                     Log("[ModPack] 已跳过 LWJGL 项")
                 Case "net.minecraft"
@@ -644,9 +652,9 @@ Retry:
                     Request.NeoForgeVersion = Component("version")
                 Case "net.fabricmc.fabric-loader"
                     Request.FabricVersion = Component("version")
-                Case "org.quiltmc.quilt-loader" 'eg. 1.0.0
-                    Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical)
-                    Throw New CancelledException
+                Case Else
+                    'Case "org.quiltmc.quilt-loader" 'eg. 1.0.0
+                    NotifyIncompatibleLoader(Component("uid"))
             End Select
         Next
         '构造加载器

+ 0 - 6
Plain Craft Launcher 2/Modules/ModMain.vb

@@ -397,12 +397,6 @@ EndHint:
     '下载页面声明
     Public FrmDownloadLeft As PageDownloadLeft
     Public FrmDownloadInstall As PageDownloadInstall
-    Public FrmDownloadClient As PageDownloadClient
-    Public FrmDownloadOptiFine As PageDownloadOptiFine
-    Public FrmDownloadLiteLoader As PageDownloadLiteLoader
-    Public FrmDownloadForge As PageDownloadForge
-    Public FrmDownloadNeoForge As PageDownloadNeoForge
-    Public FrmDownloadFabric As PageDownloadFabric
     Public FrmDownloadMod As PageDownloadMod
     Public FrmDownloadPack As PageDownloadPack
     Public FrmDownloadDataPack As PageDownloadDataPack

+ 4 - 3
Plain Craft Launcher 2/Modules/ModSecret.vb

@@ -80,15 +80,16 @@ Friend Module ModSecret
     ''' <summary>
     ''' 设置 Headers 的 UA、Referer。
     ''' </summary>
-    Friend Sub SecretHeadersSign(Url As String, ByRef Req As HttpRequestMessage, Optional UseBrowserUserAgent As Boolean = False)
+    Friend Sub SecretHeadersSign(Url As String, ByRef Req As HttpRequestMessage, Optional SimulateBrowserHeaders As Boolean = False)
+        If Req.Headers.UserAgent.Any Then Return
         If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then
             Req.Headers.Add("User-Agent", "LogStatistic")  '#4951
-        ElseIf UseBrowserUserAgent Then
+        ElseIf SimulateBrowserHeaders Then
             Req.Headers.Add("User-Agent", $"PCL2/{VersionStandardCode} Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36")
         Else
             Req.Headers.Add("User-Agent", $"PCL2/{VersionStandardCode}")
         End If
-        Req.Headers.Add("Referer", $"http://{VersionCode}.open.pcl2.server/")
+        If Not SimulateBrowserHeaders Then Req.Headers.Add("Referer", $"http://{VersionCode}.open.pcl2.server/")
         If Url.Contains("api.curseforge.com") Then Req.Headers.Add("x-api-key", CurseForgeAPIKey)
     End Sub
 

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

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

+ 1 - 1
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml

@@ -71,7 +71,7 @@
             </StackPanel>
         </local:MyCard>
         <StackPanel x:Name="PanContent">
-            <local:MyHint x:Name="HintError" HasBorder="True" Text="无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,搜索结果可能不全。&#xa;请稍后重试,或使用 VPN 以改善网络环境。" Theme="Yellow" Margin="0,0,0,15" />
+            <local:MyHint x:Name="HintError" HasBorder="True" Text="无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,搜索结果可能不全。&#xa;请稍后再试,或使用 VPN 改善网络环境。" Theme="Yellow" Margin="0,0,0,15" />
             <local:MyCard x:Name="CardProjects" UseAnimation="False">
                 <StackPanel x:Name="PanProjects" Margin="12" />
             </local:MyCard>

+ 1 - 1
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb

@@ -290,7 +290,7 @@
             Sub(MyLoader)
                 Select Case MyLoader.State
                     Case LoadState.Failed
-                        Hint(MyLoader.Name & "失败:" & GetExceptionSummary(MyLoader.Error), HintType.Critical)
+                        Hint(MyLoader.Name & "失败:" & MyLoader.Error.GetBrief(), HintType.Critical)
                     Case LoadState.Aborted
                         Hint(MyLoader.Name & "已取消!", HintType.Info)
                     Case LoadState.Loading

+ 1 - 1
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml

@@ -32,4 +32,4 @@
         <local:MyComboBoxItem Content="服务器" Tag="435/social" />
         <local:MyComboBoxItem Content="支持库" Tag="421/library" />
     </local:PageComp>
-</local:MyPageRight>
+</local:MyPageRight>

+ 1 - 1
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml

@@ -17,7 +17,7 @@
         <local:MyComboBoxItem Tag="/kitchen-sink" Content="水槽包" />
         <local:MyComboBoxItem Tag="4476/" Content="探索" />
         <local:MyComboBoxItem Tag="4477/" Content="小游戏" />
-        <local:MyComboBoxItem Tag="4471/" Content="科幻" />
+        <local:MyComboBoxItem Tag="4474/" Content="科幻" />
         <local:MyComboBoxItem Tag="4736/" Content="空岛" />
         <local:MyComboBoxItem Tag="5128/" Content="原版改良" />
         <local:MyComboBoxItem Tag="4487/" Content="FTB" ToolTip="由 FTB 官方发布" ToolTipService.HorizontalOffset="70" />

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

@@ -449,7 +449,7 @@ pause"
         '开始启动
         SyncLock InstallSyncLock
             Dim Info = New ProcessStartInfo With {
-                .FileName = Java.PathJavaw,
+                .FileName = Java.PathJava,
                 .Arguments = Arguments,
                 .UseShellExecute = False,
                 .CreateNoWindow = True,
@@ -571,7 +571,7 @@ pause"
             Dim PageData As String
             Try
                 PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile,
-                    Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", UseBrowserUserAgent:=True)
+                    Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", SimulateBrowserHeaders:=True)
                 Task.Progress = 0.8
                 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
                 Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
@@ -735,7 +735,7 @@ Retry:
             Dim PageData As String
             Try
                 PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile,
-                    Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", UseBrowserUserAgent:=True)
+                    Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", SimulateBrowserHeaders:=True)
                 Task.Progress = 0.8
                 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
                 Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
@@ -1120,7 +1120,7 @@ Retry:
         '开始启动
         SyncLock InstallSyncLock
             Dim Info = New ProcessStartInfo With {
-                .FileName = Java.PathJavaw,
+                .FileName = Java.PathJava,
                 .Arguments = Arguments,
                 .UseShellExecute = False,
                 .CreateNoWindow = True,
@@ -1647,7 +1647,7 @@ Retry:
         Sub()
             Try
                 Log("[Download] 刷新 Forge 推荐版本缓存开始")
-                Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/promos")
+                Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/promos", RequireJson:=True)
                 If Result.Length < 1000 Then Throw New Exception("获取的结果过短(" & Result & ")")
                 Dim ResultJson As JContainer = GetJson(Result)
                 '获取所有推荐版本列表
@@ -1991,7 +1991,7 @@ Retry:
             Case LoadState.Finished
                 Hint(Loader.Name & "成功!", HintType.Finish)
             Case LoadState.Failed
-                Hint(Loader.Name & "失败:" & GetExceptionSummary(Loader.Error), HintType.Critical)
+                Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical)
             Case LoadState.Aborted
                 Hint(Loader.Name & "已取消!", HintType.Info)
         End Select
@@ -2005,7 +2005,7 @@ Retry:
                 WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存)
                 Hint(Loader.Name & "成功!", HintType.Finish)
             Case LoadState.Failed
-                Hint(Loader.Name & "失败:" & GetExceptionSummary(Loader.Error), HintType.Critical)
+                Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical)
             Case LoadState.Aborted
                 Hint(Loader.Name & "已取消!", HintType.Info)
             Case LoadState.Loading

+ 0 - 31
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml

@@ -1,31 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    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="PageDownloadClient"
-    PanScroll="PanBack">
-    <Grid>
-        <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-            <StackPanel Orientation="Vertical" Margin="5">
-                <!--<local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True" Title="自动安装提示" x:Name="CardTip">
-                <StackPanel Margin="25,40,15,20">
-                    <TextBlock Margin="0,0,0,2" Text="本页只提供需要在下载后手动进行安装的游戏 jar 与 json 下载。" TextWrapping="Wrap" />
-                    <TextBlock Margin="0,0,0,11" Text="如果你想要进行自动安装,在游戏安装页选择游戏版本,然后直接安装即可。" TextWrapping="Wrap" />
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140" Text="打开官网" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                        <local:MyButton x:Name="BtnInstall" Grid.Column="1" MinWidth="140" Text="转到游戏安装页" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>-->
-                <StackPanel Name="PanMain" Margin="20,20,20,5" Grid.IsSharedSizeScope="True" />
-            </StackPanel>
-        </local:MyScrollViewer>
-        <local:MyCard 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" HorizontalAlignment="Center" VerticalAlignment="Center" />
-        </local:MyCard>
-    </Grid>
-</local:MyPageRight>

+ 0 - 111
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb

@@ -1,111 +0,0 @@
-Public Class PageDownloadClient
-
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, PanBack, Nothing, DlClientListLoader, AddressOf Load_OnFinish)
-    End Sub
-    Private Sub Init() Handles Me.Loaded
-        PanBack.ScrollToHome()
-    End Sub
-
-    Private Sub Load_OnFinish()
-        '结果数据化
-        Try
-            '归类
-            Dim Dict As New Dictionary(Of String, List(Of JObject)) From {
-                {"正式版", New List(Of JObject)}, {"预览版", New List(Of JObject)}, {"远古版", New List(Of JObject)}, {"愚人节版", New List(Of JObject)}
-            }
-            Dim Versions As JArray = DlClientListLoader.Output.Value("versions")
-            For Each Version As JObject In Versions
-                '确定分类
-                Dim Type As String = Version("type")
-                Select Case Type
-                    Case "release"
-                        Type = "正式版"
-                    Case "snapshot"
-                        Type = "预览版"
-                        'Mojang 误分类
-                        If Version("id").ToString.StartsWith("1.") AndAlso
-                            Not Version("id").ToString.ToLower.Contains("combat") AndAlso
-                            Not Version("id").ToString.ToLower.Contains("rc") AndAlso
-                            Not Version("id").ToString.ToLower.Contains("experimental") AndAlso
-                            Not Version("id").ToString.ToLower.Contains("pre") Then
-                            Type = "正式版"
-                            Version("type") = "release"
-                        End If
-                        '愚人节版本
-                        Select Case Version("id").ToString.ToLower
-                            Case "20w14infinite", "20w14∞"
-                                Type = "愚人节版"
-                                Version("id") = "20w14∞"
-                                Version("type") = "special"
-                                Version.Add("lore", GetMcFoolName(Version("id")))
-                            Case "3d shareware v1.34", "1.rv-pre1", "15w14a", "2.0", "22w13oneblockatatime", "23w13a_or_b", "24w14potato", "25w14craftmine"
-                                Type = "愚人节版"
-                                Version("type") = "special"
-                                Version.Add("lore", GetMcFoolName(Version("id")))
-                            Case Else '4/1 自动视作愚人节版
-                                Dim ReleaseDate = Version("releaseTime").Value(Of Date).ToUniversalTime().AddHours(2)
-                                If ReleaseDate.Month = 4 AndAlso ReleaseDate.Day = 1 Then
-                                    Type = "愚人节版"
-                                    Version("type") = "special"
-                                End If
-                        End Select
-                    Case "special"
-                        '已被处理的愚人节版
-                        Type = "愚人节版"
-                    Case Else
-                        Type = "远古版"
-                End Select
-                '加入辞典
-                Dict(Type).Add(Version)
-            Next
-            '排序
-            For i = 0 To Dict.Keys.Count - 1
-                Dict(Dict.Keys(i)) = Dict.Values(i).OrderByDescending(Function(v) v("releaseTime").Value(Of Date)).ToList
-            Next
-            '清空当前
-            PanMain.Children.Clear()
-            '添加最新版本
-            Dim CardInfo As New MyCard With {.Title = "最新版本", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 2}
-            Dim TopestVersions As New List(Of JObject)
-            Dim Release As JObject = Dict("正式版")(0).DeepClone()
-            Release("lore") = "最新正式版,发布于 " & Release("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm")
-            TopestVersions.Add(Release)
-            If Dict("正式版")(0)("releaseTime").Value(Of Date) < Dict("预览版")(0)("releaseTime").Value(Of Date) Then
-                Dim Snapshot As JObject = Dict("预览版")(0).DeepClone()
-                Snapshot("lore") = "最新预览版,发布于 " & Snapshot("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm")
-                TopestVersions.Add(Snapshot)
-            End If
-            Dim PanInfo As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = TopestVersions}
-            MyCard.StackInstall(PanInfo, 2)
-            CardInfo.Children.Add(PanInfo)
-            PanMain.Children.Add(CardInfo)
-            '添加其他版本
-            For Each Pair As KeyValuePair(Of String, List(Of JObject)) In Dict
-                If Not Pair.Value.Any() Then Continue For
-                '增加卡片
-                Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 2}
-                Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
-                NewCard.Children.Add(NewStack)
-                NewCard.SwapControl = NewStack
-                NewCard.IsSwapped = True
-                PanMain.Children.Add(NewCard)
-            Next
-        Catch ex As Exception
-            Log(ex, "可视化 MC 版本列表出错", LogLevel.Feedback)
-        End Try
-    End Sub
-
-    Public Sub DownloadStart(sender As MyListItem, e As Object)
-        McDownloadClient(NetPreDownloadBehaviour.HintWhileExists, sender.Title, sender.Tag("url").ToString)
-    End Sub
-
-    ''介绍栏
-    'Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click
-    '    OpenWebsite("https://www.minecraft.net/zh-hans")
-    'End Sub
-    'Private Sub BtnInstall_Click(sender As Object, e As EventArgs) Handles BtnInstall.Click
-    '    FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadInstall)
-    'End Sub
-
-End Class

+ 0 - 28
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml

@@ -1,28 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    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="PageDownloadFabric"
-    PanScroll="PanBack">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel Orientation="Vertical" Margin="5">
-            <local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True" Title="Fabric 简介" x:Name="CardTip">
-                <StackPanel Margin="25,40,15,20">
-                    <TextBlock Margin="0,0,0,11" Text="Fabric Loader 是新版 Minecraft 下的轻量化 Mod 加载器,你需要先安装它才能安装各种 Fabric 模组。&#xa;本页面提供 Fabric 安装器下载,在下载后你需要手动打开安装器进行安装。" TextWrapping="Wrap" />
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140" Text="打开官网" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <local:MyCard x:Name="CardVersions" Margin="20,15,20,20" Title="版本列表">
-                <StackPanel x:Name="PanVersions" Margin="20,40,18,20" />
-            </local:MyCard>
-            <local:MyCard HorizontalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="40,50">
-                <local:MyLoading Text="正在获取 Fabric 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>

+ 0 - 32
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml.vb

@@ -1,32 +0,0 @@
-Public Class PageDownloadFabric
-
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, CardVersions, CardTip, DlFabricListLoader, AddressOf Load_OnFinish)
-    End Sub
-    Private Sub Init() Handles Me.Loaded
-        PanBack.ScrollToHome()
-    End Sub
-
-    Private Sub Load_OnFinish()
-        '结果数据化
-        Try
-            Dim Versions As JArray = DlFabricListLoader.Output.Value("installer")
-            PanVersions.Children.Clear()
-            For Each Version In Versions
-                PanVersions.Children.Add(FabricDownloadListItem(Version, AddressOf Fabric_Selected))
-            Next
-            CardVersions.Title = "版本列表 (" & Versions.Count & ")"
-        Catch ex As Exception
-            Log(ex, "可视化 Fabric 版本列表出错", LogLevel.Feedback)
-        End Try
-    End Sub
-
-    Private Sub Fabric_Selected(sender As MyListItem, e As EventArgs)
-        McDownloadFabricLoaderSave(sender.Tag)
-    End Sub
-
-    Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click
-        OpenWebsite("https://www.fabricmc.net")
-    End Sub
-
-End Class

+ 0 - 26
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml

@@ -1,26 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    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="PageDownloadForge"
-    PanScroll="PanBack">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel Orientation="Vertical" Margin="5">
-            <local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True" Title="Forge 简介" x:Name="CardTip">
-                <StackPanel Margin="25,40,15,20">
-                    <TextBlock Name="LabConnect" Margin="0,0,0,11" Text="Forge 是一个 Mod 加载器,你需要先安装 Forge 才能安装各种 Forge 模组。" TextWrapping="Wrap" />
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140" Text="打开官网" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
-            <local:MyCard HorizontalAlignment="Center" Margin="40,50" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False">
-                <local:MyLoading Text="正在获取 Forge 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>

+ 0 - 61
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb

@@ -1,61 +0,0 @@
-Public Class PageDownloadForge
-
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlForgeListLoader, AddressOf Load_OnFinish)
-    End Sub
-    Private Sub Init() Handles Me.Loaded
-        PanBack.ScrollToHome()
-    End Sub
-
-    Private Sub Load_OnFinish()
-        '结果数据化
-        Try
-            '清空当前
-            PanMain.Children.Clear()
-            '转化为 UI
-            For Each Version As String In DlForgeListLoader.Output.Value.Sort(AddressOf VersionSortBoolean)
-                '增加卡片
-                Dim NewCard As New MyCard With {.Title = Version.Replace("_p", " P"), .Margin = New Thickness(0, 0, 0, 15), .SwapType = 5}
-                Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Version}
-                NewCard.Children.Add(NewStack)
-                NewCard.SwapControl = NewStack
-                NewCard.IsSwapped = True
-                PanMain.Children.Add(NewCard)
-            Next
-            ''非官方源警示
-            'If Setup.Get("ToolDownloadOutOfDate") AndAlso Not DlForgeListLoader.Output.IsOfficial Then
-            '    Dim CardWarn As New MyCard With {.Title = "过期提示", .Margin = New Thickness(0, 0, 0, 15)}
-            '    CardWarn.Children.Add(New TextBlock With {
-            '                          .Margin = New Thickness(25, MyCard.SwapedHeight, 15, 15), .VerticalAlignment = VerticalAlignment.Top, .HorizontalAlignment = HorizontalAlignment.Left, .TextTrimming = TextTrimming.None, .TextWrapping = TextWrapping.Wrap,
-            '                          .Text = "获取官方源失败,正在使用 " & DlForgeListLoader.Output.SourceName & " 镜像源,版本列表可能并非最新。" & vbCrLf & "官方源错误原因:" & If(DlForgeListLoader.Output.OfficialError, New Exception("连接服务器超时")).Message})
-            '    PanMain.Children.Insert(0, CardWarn)
-            'End If
-        Catch ex As Exception
-            Log(ex, "可视化 Forge 版本列表出错", LogLevel.Feedback)
-        End Try
-    End Sub
-
-    'Forge 版本列表加载
-    Public Sub Forge_Click(sender As MyLoading, e As MouseButtonEventArgs)
-        If sender.State.LoadingState = MyLoading.MyLoadingState.Error Then
-            CType(sender.State, LoaderTask(Of String, List(Of DlForgeVersionEntry))).Start(IsForceRestart:=True)
-        End If
-    End Sub
-    Public Sub Forge_StateChanged(sender As MyLoading, newState As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState)
-        If newState <> MyLoading.MyLoadingState.Stop Then Return
-
-        Dim Card As MyCard = CType(sender.Parent, FrameworkElement).Parent
-        Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = sender.State
-        '载入列表
-        Card.SwapControl.Children.Clear()
-        Card.SwapControl.Tag = Loader.Output
-        Card.SwapType = 6
-        Card.StackInstall()
-    End Sub
-
-    '介绍栏
-    Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click
-        OpenWebsite("https://files.minecraftforge.net")
-    End Sub
-
-End Class

+ 1 - 1
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml

@@ -196,4 +196,4 @@
             </StackPanel>
         </Border>
     </Grid>
--->
+-->

File diff suppressed because it is too large
+ 3 - 26
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml


+ 1 - 88
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb

@@ -11,7 +11,7 @@
     ''' <summary>
     ''' 勾选事件改变页面。
     ''' </summary>
-    Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemInstall.Check, ItemClient.Check, ItemOptiFine.Check, ItemForge.Check, ItemNeoForge.Check, ItemLiteLoader.Check, ItemMod.Check, ItemFabric.Check, ItemPack.Check, ItemResourcePack.Check, ItemShader.Check, ItemDataPack.Check
+    Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemInstall.Check, ItemMod.Check, ItemPack.Check, ItemResourcePack.Check, ItemShader.Check, ItemDataPack.Check
         '尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0
         '若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False)
         If sender.Tag IsNot Nothing Then PageChange(Val(sender.Tag))
@@ -23,24 +23,6 @@
             Case FormMain.PageSubType.DownloadInstall
                 If FrmDownloadInstall Is Nothing Then FrmDownloadInstall = New PageDownloadInstall
                 Return FrmDownloadInstall
-            Case FormMain.PageSubType.DownloadClient
-                If FrmDownloadClient Is Nothing Then FrmDownloadClient = New PageDownloadClient
-                Return FrmDownloadClient
-            Case FormMain.PageSubType.DownloadOptiFine
-                If FrmDownloadOptiFine Is Nothing Then FrmDownloadOptiFine = New PageDownloadOptiFine
-                Return FrmDownloadOptiFine
-            Case FormMain.PageSubType.DownloadForge
-                If FrmDownloadForge Is Nothing Then FrmDownloadForge = New PageDownloadForge
-                Return FrmDownloadForge
-            Case FormMain.PageSubType.DownloadNeoForge
-                If FrmDownloadNeoForge Is Nothing Then FrmDownloadNeoForge = New PageDownloadNeoForge
-                Return FrmDownloadNeoForge
-            Case FormMain.PageSubType.DownloadLiteLoader
-                If FrmDownloadLiteLoader Is Nothing Then FrmDownloadLiteLoader = New PageDownloadLiteLoader
-                Return FrmDownloadLiteLoader
-            Case FormMain.PageSubType.DownloadFabric
-                If FrmDownloadFabric Is Nothing Then FrmDownloadFabric = New PageDownloadFabric
-                Return FrmDownloadFabric
             Case FormMain.PageSubType.DownloadMod
                 If FrmDownloadMod Is Nothing Then FrmDownloadMod = New PageDownloadMod
                 Return FrmDownloadMod
@@ -163,24 +145,6 @@
                     FrmDownloadDataPack.PageLoaderRestart()
                 End If
                 ItemDataPack.Checked = True
-            Case FormMain.PageSubType.DownloadClient
-                DlClientListLoader.Start(IsForceRestart:=True)
-                ItemClient.Checked = True
-            Case FormMain.PageSubType.DownloadOptiFine
-                DlOptiFineListLoader.Start(IsForceRestart:=True)
-                ItemOptiFine.Checked = True
-            Case FormMain.PageSubType.DownloadForge
-                DlForgeListLoader.Start(IsForceRestart:=True)
-                ItemForge.Checked = True
-            Case FormMain.PageSubType.DownloadNeoForge
-                DlNeoForgeListLoader.Start(IsForceRestart:=True)
-                ItemNeoForge.Checked = True
-            Case FormMain.PageSubType.DownloadLiteLoader
-                DlLiteLoaderListLoader.Start(IsForceRestart:=True)
-                ItemLiteLoader.Checked = True
-            Case FormMain.PageSubType.DownloadFabric
-                DlFabricListLoader.Start(IsForceRestart:=True)
-                ItemFabric.Checked = True
         End Select
         Hint("正在刷新……", Log:=False)
     End Sub
@@ -191,55 +155,4 @@
         FrmDownloadInstall.ExitSelectPage()
     End Sub
 
-    '展开手动安装
-    Private Sub ItemHand_Click(sender As Object, e As RouteEventArgs) Handles ItemHand.Changed
-        If ItemHand.Checked = False Then Return
-        e.Handled = True
-        AniControlEnabled += 1
-        If Not Setup.Get("HintHandInstall") Then
-            Setup.Set("HintHandInstall", True)
-            If MyMsgBox("手动安装包功能提供了 OptiFine、Forge 等组件的 .jar 安装文件下载,但无法自动安装。" & vbCrLf &
-                        "在自动安装页面先选择 MC 版本,然后就可以选择 OptiFine、Forge 等组件,让 PCL 自动进行安装了。", "自动安装提示", "返回自动安装", "继续下载手动安装包") = 1 Then
-                FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.Download}, FormMain.PageSubType.DownloadInstall)
-                AniControlEnabled -= 1
-                Return
-            End If
-        End If
-        ItemHand.Visibility = Visibility.Collapsed
-        LabGame.Visibility = Visibility.Collapsed
-        LabHand.Visibility = Visibility.Visible
-        ItemClient.Visibility = Visibility.Visible
-        ItemOptiFine.Visibility = Visibility.Visible
-        ItemFabric.Visibility = Visibility.Visible
-        ItemForge.Visibility = Visibility.Visible
-        ItemNeoForge.Visibility = Visibility.Visible
-        ItemLiteLoader.Visibility = Visibility.Visible
-        RunInThread(
-        Sub()
-            Thread.Sleep(20)
-            RunInUiWait(Sub() ItemClient.SetChecked(True, True, True))
-            AniControlEnabled -= 1
-        End Sub)
-    End Sub
-    '折叠手动安装
-    Private Sub LabHand_Click(sender As Object, e As MouseButtonEventArgs) Handles LabHand.MouseLeftButtonUp
-        e.Handled = True
-        AniControlEnabled += 1
-        ItemHand.Visibility = Visibility.Visible
-        LabGame.Visibility = Visibility.Visible
-        LabHand.Visibility = Visibility.Collapsed
-        ItemClient.Visibility = Visibility.Collapsed
-        ItemOptiFine.Visibility = Visibility.Collapsed
-        ItemNeoForge.Visibility = Visibility.Collapsed
-        ItemFabric.Visibility = Visibility.Collapsed
-        ItemForge.Visibility = Visibility.Collapsed
-        ItemLiteLoader.Visibility = Visibility.Collapsed
-        RunInThread(
-        Sub()
-            Thread.Sleep(20)
-            RunInUiWait(Sub() ItemInstall.SetChecked(True, True, True))
-            AniControlEnabled -= 1
-        End Sub)
-    End Sub
-
 End Class

+ 0 - 26
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml

@@ -1,26 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    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="PageDownloadLiteLoader"
-    PanScroll="PanBack">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel Orientation="Vertical" Margin="5">
-            <local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True" Title="LiteLoader 简介" x:Name="CardTip">
-                <StackPanel Margin="25,40,15,20">
-                    <TextBlock Name="LabConnect" Margin="0,0,0,11" Text="与 Forge 类似,LiteLoader 可以用于加载老版本 Minecraft 中的 LiteLoader 模组。" TextWrapping="Wrap" />
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140" Text="打开官网" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
-            <local:MyCard HorizontalAlignment="Center" Margin="40,50" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False">
-                <local:MyLoading Text="正在获取 LiteLoader 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>

+ 0 - 52
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb

@@ -1,52 +0,0 @@
-Public Class PageDownloadLiteLoader
-
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlLiteLoaderListLoader, AddressOf Load_OnFinish)
-    End Sub
-    Private Sub Init() Handles Me.Loaded
-        PanBack.ScrollToHome()
-    End Sub
-
-    Private Sub Load_OnFinish()
-        '结果数据化
-        Try
-            '归类
-            Dim Dict As New Dictionary(Of String, List(Of DlLiteLoaderListEntry))
-            For VersionCode As Integer = 30 To 0 Step -1
-                Dict.Add("1." & VersionCode, New List(Of DlLiteLoaderListEntry))
-            Next
-            Dict.Add("未知版本", New List(Of DlLiteLoaderListEntry))
-            For Each Version As DlLiteLoaderListEntry In DlLiteLoaderListLoader.Output.Value
-                Dim MainVersion As String = "1." & Version.Inherit.Split(".")(1)
-                If Dict.ContainsKey(MainVersion) Then
-                    Dict(MainVersion).Add(Version)
-                Else
-                    Dict("未知版本").Add(Version)
-                End If
-            Next
-            '清空当前
-            PanMain.Children.Clear()
-            '转化为 UI
-            For Each Pair As KeyValuePair(Of String, List(Of DlLiteLoaderListEntry)) In Dict
-                If Not Pair.Value.Any() Then Continue For
-                '增加卡片
-                Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 10}
-                Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
-                NewCard.Children.Add(NewStack)
-                NewCard.SwapControl = NewStack
-                NewCard.IsSwapped = True
-                PanMain.Children.Add(NewCard)
-            Next
-        Catch ex As Exception
-            Log(ex, "可视化 LiteLoader 版本列表出错", LogLevel.Feedback)
-        End Try
-    End Sub
-
-    Public Sub DownloadStart(sender As MyListItem, e As Object)
-        McDownloadLiteLoader(sender.Tag)
-    End Sub
-    Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click
-        OpenWebsite("https://www.liteloader.com")
-    End Sub
-
-End Class

+ 0 - 27
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml

@@ -1,27 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    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="PageDownloadNeoForge"
-    PanScroll="PanBack">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel Orientation="Vertical" Margin="5">
-            <local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True" Title="NeoForge 简介" x:Name="CardTip">
-                <StackPanel Margin="25,40,15,20">
-                    <TextBlock Margin="0,0,0,11" Text="NeoForge 是 Minecraft 1.20.1+ 的 Mod 加载器,你需要先安装它才能安装各种 NeoForge 模组,它也兼容一些 Forge 模组。&#xa;本页面提供 NeoForge 安装器下载,在下载后你需要手动打开安装器进行安装。" TextWrapping="Wrap" />
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140" Text="打开官网" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight"
-                                        EventType="打开网页" EventData="https://neoforged.net/" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
-            <local:MyCard HorizontalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="40,50">
-                <local:MyLoading Text="正在获取 NeoForge 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>

+ 0 - 34
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb

@@ -1,34 +0,0 @@
-Public Class PageDownloadNeoForge
-
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlNeoForgeListLoader, AddressOf Load_OnFinish)
-    End Sub
-    Private Sub Init() Handles Me.Loaded
-        PanBack.ScrollToHome()
-    End Sub
-
-    Private Sub Load_OnFinish()
-        '结果数据化
-        Try
-            '归类
-            Dim Dict = DlNeoForgeListLoader.Output.Value.GroupBy(Function(d) d.Inherit).OrderByDescending(Function(g) g.Key).
-                ToDictionary(Function(g) g.Key, Function(g) g.ToList())
-            '清空当前
-            PanMain.Children.Clear()
-            '转化为 UI
-            For Each Pair As KeyValuePair(Of String, List(Of DlNeoForgeListEntry)) In Dict
-                If Not Pair.Value.Any() Then Continue For
-                '增加卡片
-                Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 13}
-                Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
-                NewCard.Children.Add(NewStack)
-                NewCard.SwapControl = NewStack
-                NewCard.IsSwapped = True
-                PanMain.Children.Add(NewCard)
-            Next
-        Catch ex As Exception
-            Log(ex, "可视化 NeoForge 版本列表出错", LogLevel.Feedback)
-        End Try
-    End Sub
-
-End Class

+ 0 - 26
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml

@@ -1,26 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    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="PageDownloadOptiFine"
-    PanScroll="PanBack">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel Orientation="Vertical" Margin="5">
-            <local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True" Title="OptiFine 简介" x:Name="CardTip">
-                <StackPanel Margin="25,40,15,20">
-                    <TextBlock Name="LabConnect" Margin="0,0,0,11" Text="OptiFine 又称为高清修复,以允许安装光影、使用高清材质、提高游戏性能,但与 Mod 的兼容性不佳。" TextWrapping="Wrap" />
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140" Text="打开官网" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
-            <local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="40,50">
-                <local:MyLoading Text="正在获取 OptiFine 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>

+ 0 - 53
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb

@@ -1,53 +0,0 @@
-Public Class PageDownloadOptiFine
-
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlOptiFineListLoader, AddressOf Load_OnFinish)
-    End Sub
-    Private Sub Init() Handles Me.Loaded
-        PanBack.ScrollToHome()
-    End Sub
-
-    Private Sub Load_OnFinish()
-        '结果数据化
-        Try
-            '归类
-            Dim Dict As New Dictionary(Of String, List(Of DlOptiFineListEntry))
-            Dict.Add("快照版本", New List(Of DlOptiFineListEntry))
-            For VersionCode As Integer = 50 To 0 Step -1
-                Dict.Add("1." & VersionCode, New List(Of DlOptiFineListEntry))
-            Next
-            For Each Version As DlOptiFineListEntry In DlOptiFineListLoader.Output.Value
-                If Version.Inherit.StartsWith("1.") Then
-                    Dim MainVersion As String = "1." & Version.NameDisplay.Split(".")(1).Split(" ")(0)
-                    If Dict.ContainsKey(MainVersion) Then
-                        Dict(MainVersion).Add(Version)
-                    Else
-                        Dict("快照版本").Add(Version)
-                    End If
-                Else
-                    Dict("快照版本").Add(Version)
-                End If
-            Next
-            '清空当前
-            PanMain.Children.Clear()
-            '转化为 UI
-            For Each Pair As KeyValuePair(Of String, List(Of DlOptiFineListEntry)) In Dict
-                If Not Pair.Value.Any() Then Continue For
-                '增加卡片
-                Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 3}
-                Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
-                NewCard.Children.Add(NewStack)
-                NewCard.SwapControl = NewStack
-                NewCard.IsSwapped = True
-                PanMain.Children.Add(NewCard)
-            Next
-        Catch ex As Exception
-            Log(ex, "可视化 OptiFine 版本列表出错", LogLevel.Feedback)
-        End Try
-    End Sub
-
-    Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click
-        OpenWebsite("https://www.optifine.net/")
-    End Sub
-
-End Class

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

@@ -87,7 +87,7 @@
         LabTitle.Text = "登录 Minecraft"
         LabCaption.Text =
             $"登录网页将自动开启,请在网页中输入 {UserCode}(已自动复制)。" & vbCrLf & vbCrLf &
-            $"如果网络环境不佳,网页可能一直加载不出来,届时请使用加速器或 VPN 以改善网络环境。" & vbCrLf &
+            $"如果网络环境不佳,网页可能一直加载不出来,届时请使用加速器或 VPN 改善网络环境。" & vbCrLf &
             $"你也可以用其他设备打开 {Website} 并输入上述代码。"
         Btn1.EventData = Website
         Btn2.EventData = UserCode
@@ -114,7 +114,7 @@
                         "scope=XboxLive.signin%20offline_access",
                     ContentType:="application/x-www-form-urlencoded",
                     Timeout:=5000 + UnknownFailureCount * 5000,
-                    MakeLog:=False)
+                    MakeLog:=False, RequireJson:=True)
                 '获取结果
                 Dim ResultJson As JObject = GetJson(Result)
                 McLaunchLog($"令牌过期时间:{ResultJson("expires_in")} 秒")
@@ -122,6 +122,7 @@
                 Finished({ResultJson("access_token").ToString, ResultJson("refresh_token").ToString})
                 Return
             Catch ex As Exception
+                '修改错误列表时,同时修改 ModLaunch.MsLoginStep1Refresh 中的对应代码
                 If TypeOf ex Is ResponsedWebException Then
                     Dim Response = CType(ex, ResponsedWebException).Response
                     If Response.Contains("authorization_declined") Then

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

@@ -392,14 +392,14 @@ UnknownType:
             Result = McSkinDownload(Result)
             If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & UserName)
             Data.Output = Result
+        Catch ex As ThreadInterruptedException
+            Data.Output = ""
+            Return
         Catch ex As Exception
-            If ex.GetType.Name = "ThreadInterruptedException" Then
-                Data.Output = ""
-                Return
-            ElseIf GetExceptionSummary(ex).Contains("429") Then
+            If ex.GetBrief.Contains("(429)") Then
                 Data.Output = PathImage & "Skins/" & McSkinSex(McLoginLegacyUuid(UserName)) & ".png"
                 Log("[Minecraft] 获取正版皮肤失败(" & UserName & "):获取皮肤太过频繁,请 5 分钟后再试!", LogLevel.Hint)
-            ElseIf GetExceptionSummary(ex).Contains("未设置自定义皮肤") Then
+            ElseIf ex.GetBrief.Contains("未设置自定义皮肤") Then
                 Data.Output = PathImage & "Skins/" & McSkinSex(McLoginLegacyUuid(UserName)) & ".png"
                 Log("[Minecraft] 用户未设置自定义皮肤,跳过皮肤加载")
             Else
@@ -462,11 +462,11 @@ UseDefault:
                         If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & ID)
                         Data.Output = Result
                     End If
+                Catch ex As ThreadInterruptedException
+                    Data.Output = ""
+                    Return
                 Catch ex As Exception
-                    If ex.GetType.Name = "ThreadInterruptedException" Then
-                        Data.Output = ""
-                        Return
-                    ElseIf GetExceptionSummary(ex).Contains("429") Then
+                    If ex.GetBrief.Contains("(429)") Then
                         Data.Output = PathImage & "Skins/" & McSkinSex(McLoginLegacyUuid(ID)) & ".png"
                         Log("获取离线登录使用的正版皮肤失败(" & ID & "):获取皮肤太过频繁,请 5 分钟后再试!")
                     Else
@@ -514,14 +514,14 @@ UseDefault:
             Result = McSkinDownload(Result)
             If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & UserName)
             Data.Output = Result
+        Catch ex As ThreadInterruptedException
+            Data.Output = ""
+            Return
         Catch ex As Exception
-            If ex.GetType.Name = "ThreadInterruptedException" Then
-                Data.Output = ""
-                Return
-            ElseIf GetExceptionSummary(ex).Contains("429") Then
+            If ex.GetBrief.Contains("(429)") Then
                 Data.Output = PathImage & "Skins/Steve.png"
                 Log("[Minecraft] 获取统一通行证皮肤失败(" & UserName & "):获取皮肤太过频繁,请 5 分钟后再试!", LogLevel.Hint)
-            ElseIf GetExceptionSummary(ex).Contains("未设置自定义皮肤") Then
+            ElseIf ex.GetBrief.Contains("未设置自定义皮肤") Then
                 Data.Output = PathImage & "Skins/Steve.png"
                 Log("[Minecraft] 用户未设置自定义皮肤,跳过皮肤加载")
             Else
@@ -550,26 +550,26 @@ Finish:
         RunInUi(Sub() If FrmLoginAuthSkin IsNot Nothing AndAlso FrmLoginAuthSkin.Skin IsNot Nothing Then FrmLoginAuthSkin.Skin.Clear())
         '获取 Url
         Dim UserName As String = Data.Input(0)
-        Dim Uuid As String = Data.Input(1)
+        Dim UUID As String = Data.Input(1)
         If UserName = "" Then
             Data.Output = PathImage & "Skins/Steve.png"
             Log("[Minecraft] 获取 Authlib-Injector 皮肤失败,ID 为空")
             GoTo Finish
         End If
         Try
-            Dim Result As String = McSkinGetAddress(Uuid, "Auth")
+            Dim Result As String = McSkinGetAddress(UUID, "Auth")
             If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & UserName)
             Result = McSkinDownload(Result)
             If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & UserName)
             Data.Output = Result
+        Catch ex As ThreadInterruptedException
+            Data.Output = ""
+            Return
         Catch ex As Exception
-            If ex.GetType.Name = "ThreadInterruptedException" Then
-                Data.Output = ""
-                Return
-            ElseIf GetExceptionSummary(ex).Contains("429") Then
+            If ex.GetBrief.Contains("(429)") Then
                 Data.Output = PathImage & "Skins/Steve.png"
                 Log("[Minecraft] 获取 Authlib-Injector 皮肤失败(" & UserName & "):获取皮肤太过频繁,请 5 分钟后再试!", LogLevel.Hint)
-            ElseIf GetExceptionSummary(ex).Contains("未设置自定义皮肤") Then
+            ElseIf ex.GetBrief.Contains("未设置自定义皮肤") Then
                 Data.Output = PathImage & "Skins/Steve.png"
                 Log("[Minecraft] 用户未设置自定义皮肤,跳过皮肤加载")
             Else

+ 80 - 77
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb

@@ -1,5 +1,7 @@
-Public Class PageLaunchRight
-    Implements IRefreshable
+Imports System.Windows.Threading
+
+Public Class PageLaunchRight
+    Implements IRefreshable, IDispatcherUnhandledException
 
     Private Sub Init() Handles Me.Loaded
         PanBack.ScrollToHome()
@@ -27,7 +29,7 @@
     ''' 刷新主页。
     ''' </summary>
     Private Sub Refresh() Handles Me.Loaded
-        RunInNewThread(
+        RunInThread(
         Sub()
             Try
                 SyncLock RefreshLock
@@ -36,37 +38,20 @@
             Catch ex As Exception
                 Log(ex, "加载 PCL 主页自定义信息失败", If(ModeDebug, LogLevel.Msgbox, LogLevel.Hint))
             End Try
-        End Sub, $"刷新主页 #{GetUuid()}")
+        End Sub)
     End Sub
     Private Sub RefreshReal()
-        Dim Content As String = ""
-        Dim Url As String
+        Dim Content As String = Nothing, Url As String = Nothing
         Select Case Setup.Get("UiCustomType")
             Case 1
                 '加载本地文件
                 Log("[Page] 主页自定义数据来源:本地文件")
                 Content = ReadFile(Path & "PCL\Custom.xaml") 'ReadFile 会进行存在检测
             Case 2
+                '联网下载
                 Url = Setup.Get("UiCustomNet")
-Download:
-                '加载联网文件
-                If String.IsNullOrWhiteSpace(Url) Then Exit Select
-                If Url = Setup.Get("CacheSavedPageUrl") AndAlso File.Exists(PathTemp & "Cache\Custom.xaml") Then
-                    '缓存可用
-                    Log("[Page] 主页自定义数据来源:联网缓存文件")
-                    Content = ReadFile(PathTemp & "Cache\Custom.xaml")
-                    '后台更新缓存
-                    OnlineLoader.Start(Url)
-                Else
-                    '缓存不可用
-                    Log("[Page] 主页自定义数据来源:联网全新下载")
-                    Hint("正在加载主页……")
-                    RunInUiWait(Sub() LoadContent("")) '在加载结束前清空页面
-                    Setup.Set("CacheSavedPageVersion", "")
-                    OnlineLoader.Start(Url) '下载完成后将会再次触发更新
-                    Return
-                End If
             Case 3
+                '预设
                 Select Case Setup.Get("UiCustomPreset")
                     Case 0
                         Log("[Page] 主页预设:你知道吗")
@@ -88,50 +73,58 @@ Download:
                             </local:MyCard>"
                     Case 2
                         Log("[Page] 主页预设:Minecraft 新闻")
-                        Url = "https://pcl.mcnews.thestack.top"
-                        GoTo Download
+                        Url = "https://mcnews.meloong.com"
                     Case 3
                         Log("[Page] 主页预设:简单主页")
                         Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/MFn233/Custom.xaml"
-                        GoTo Download
                     Case 4
                         Log("[Page] 主页预设:每日整合包推荐")
                         Url = "https://pclsub.sodamc.com/"
-                        GoTo Download
                     Case 5
                         Log("[Page] 主页预设:Minecraft 皮肤推荐")
                         Url = "https://forgepixel.com/pcl_sub_file"
-                        GoTo Download
                     Case 6
                         Log("[Page] 主页预设:OpenBMCLAPI 仪表盘 Lite")
                         Url = "https://pcl-bmcl.milu.ink/"
-                        GoTo Download
                     Case 7
                         Log("[Page] 主页预设:主页市场")
                         Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/JingHai-Lingyun/Custom.xaml"
-                        GoTo Download
                     Case 8
                         Log("[Page] 主页预设:更新日志")
                         Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/Joker2184/UpdateHomepage.xaml"
-                        GoTo Download
                     Case 9
                         Log("[Page] 主页预设:PCL 新功能说明书")
                         Url = "https://raw.gitcode.com/WForst-Breeze/WhatsNewPCL/raw/main/Custom.xaml"
-                        GoTo Download
                     Case 10
                         Log("[Page] 主页预设:OpenMCIM Dashboard")
                         Url = "https://files.mcimirror.top/PCL"
-                        GoTo Download
                     Case 11
                         Log("[Page] 主页预设:杂志主页")
                         Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/Ext1nguisher/Custom.xaml"
-                        GoTo Download
                     Case 12
                         Log("[Page] 主页预设:PCL GitHub 仪表盘")
                         Url = "https://ddf.pcl-community.org/Custom.xaml"
-                        GoTo Download
                 End Select
         End Select
+        '联网下载
+        If Not String.IsNullOrWhiteSpace(Url) Then
+            If Url = Setup.Get("CacheSavedPageUrl") AndAlso File.Exists(PathTemp & "Cache\Custom.xaml") Then
+                '缓存可用
+                Log("[Page] 主页自定义数据来源:联网缓存文件")
+                Content = ReadFile(PathTemp & "Cache\Custom.xaml")
+                '后台更新缓存
+                OnlineLoader.Start(Url)
+            Else
+                '缓存不可用
+                Log("[Page] 主页自定义数据来源:联网全新下载")
+                Hint("正在加载主页……")
+                RunInUiWait(Sub() LoadContent(Nothing)) '在加载结束前清空页面
+                Setup.Set("CacheSavedPageVersion", "")
+                OnlineLoader.Start(Url) '下载完成后将会再次触发更新
+                Return
+            End If
+        End If
+        '同步到 UI
         RunInUi(Sub() LoadContent(Content))
     End Sub
     Private RefreshLock As New Object
@@ -169,15 +162,13 @@ Download:
                 Log($"[Page] 无法检查联网主页版本,将直接下载,检查源:{VersionAddress}")
             End Try
             '实际下载
-            If NeedDownload Then
-                Dim FileContent As String = NetRequestByClientRetry(Address)
-                Log($"[Page] 已联网下载主页,内容长度:{FileContent.Length},来源:{Address}")
-                Setup.Set("CacheSavedPageUrl", Address)
-                Setup.Set("CacheSavedPageVersion", Version)
-                WriteFile(PathTemp & "Cache\Custom.xaml", FileContent)
-            End If
-            '要求刷新
-            RunInUi(AddressOf Refresh) '不直接调用 Refresh,以防止死循环(#6245)
+            If Not NeedDownload Then Return
+            Dim FileContent As String = NetRequestByClientRetry(Address)
+            Log($"[Page] 已联网下载主页,内容长度:{FileContent.Length},来源:{Address}")
+            Setup.Set("CacheSavedPageUrl", Address)
+            Setup.Set("CacheSavedPageVersion", Version)
+            WriteFile(PathTemp & "Cache\Custom.xaml", FileContent)
+            Refresh() '要求刷新
         Catch ex As Exception
             Log(ex, $"下载主页失败({Address})", If(ModeDebug, LogLevel.Msgbox, LogLevel.Hint))
         End Try
@@ -215,19 +206,19 @@ Download:
     ''' 必须在 UI 线程调用。
     ''' </summary>
     Private Sub LoadContent(Content As String)
-        SyncLock LoadContentLock
-            '如果加载目标内容一致则不加载
-            Dim Hash = Content.GetHashCode()
-            If Hash = LoadedContentHash Then Return
-            LoadedContentHash = Hash
-            '实际加载内容
-            PanCustom.Children.Clear()
-            If String.IsNullOrWhiteSpace(Content) Then
-                Log($"[Page] 实例化:清空主页 UI,来源为空")
-                Return
-            End If
-            Dim LoadStartTime As Date = Date.Now
-            Try
+        Try
+            SyncLock LoadContentLock
+                '如果加载目标内容一致则不加载
+                Dim Hash = If(Content, "").GetHashCode()
+                If Hash = LoadedContentHash Then Return
+                LoadedContentHash = Hash
+                '实际加载内容
+                PanCustom.Children.Clear()
+                If String.IsNullOrWhiteSpace(Content) Then
+                    Log($"[Page] 实例化:清空主页 UI,来源为空")
+                    Return
+                End If
+                Dim LoadStartTime As Date = Date.Now
                 '修改时应同时修改 PageOtherHelpDetail.Init
                 Content = HelpArgumentReplace(Content)
                 Do While Content.Contains("xmlns")
@@ -236,26 +227,38 @@ Download:
                 Content = "<StackPanel xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:sys=""clr-namespace:System;assembly=mscorlib"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:local=""clr-namespace:PCL;assembly=Plain Craft Launcher 2"">" & Content & "</StackPanel>"
                 Log($"[Page] 实例化:加载主页 UI 开始,最终内容长度:{Content.Count}")
                 PanCustom.Children.Add(GetObjectFromXML(Content))
-            Catch ex As Exception
-                If ModeDebug Then
-                    Log(ex, "加载失败的主页内容:" & vbCrLf & Content)
-                    If MyMsgBox(If(TypeOf ex Is UnauthorizedAccessException, ex.Message, $"主页内容编写有误,请根据下列错误信息进行检查:{vbCrLf}{GetExceptionSummary(ex)}"),
-                                "加载主页界面失败", "重试", "取消") = 1 Then
-                        GoTo Refresh '防止 SyncLock 死锁
-                    End If
-                Else
-                    Log(ex, "加载主页界面失败", LogLevel.Hint)
-                End If
-                Return
-            End Try
-            Dim LoadCostTime = (Date.Now - LoadStartTime).Milliseconds
-            Log($"[Page] 实例化:加载主页 UI 完成,耗时 {LoadCostTime}ms")
-            If LoadCostTime > 3000 Then Hint($"主页加载过于缓慢(花费了 {Math.Round(LoadCostTime / 1000, 1)} 秒),请向主页作者反馈此问题,或暂时停止使用该主页")
-        End SyncLock
-        Return
-Refresh:
-        ForceRefresh()
+                '加载计时
+                Dim LoadCostTime = (Date.Now - LoadStartTime).Milliseconds
+                Log($"[Page] 实例化:加载主页 UI 完成,耗时 {LoadCostTime}ms")
+                If LoadCostTime > 3000 Then Hint($"主页加载过于缓慢(花费了 {Math.Round(LoadCostTime / 1000, 1)} 秒),请向主页作者反馈此问题,或暂时停止使用该主页")
+            End SyncLock
+        Catch ex As Exception
+            Log(ex, "加载失败的主页内容:" & vbCrLf & Content)
+            OnLoadContentFailed(ex)
+        End Try
     End Sub
+    ''' <summary>
+    ''' 加载主页失败时调用。
+    ''' </summary>
+    Private Sub OnLoadContentFailed(ex As Exception)
+        If ModeDebug OrElse Setup.Get("UiCustomType") = 1 Then
+            Log(ex, "加载主页失败")
+            If MyMsgBox(If(TypeOf ex Is UnauthorizedAccessException, ex.Message, $"主页内容编写有误,请根据下列错误信息进行检查:{vbCrLf}{ex.GetBrief}"),
+                        "加载主页失败", "重试", "取消") = 1 Then ForceRefresh()
+        Else
+            Log(ex, "加载主页失败", LogLevel.Hint)
+        End If
+    End Sub
+    ''' <summary>
+    ''' 捕获主页在 Measure 和 Arrange 阶段抛出的异常。
+    ''' </summary>
+    Private Sub DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) Implements IDispatcherUnhandledException.DispatcherUnhandledException
+        If TypeOf e.Exception IsNot Markup.XamlParseException Then Return
+        e.Handled = True
+        LoadContent(Nothing)
+        OnLoadContentFailed(e.Exception)
+    End Sub
+
     Private LoadedContentHash As Integer = -1
     Private LoadContentLock As New Object
 

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

@@ -86,22 +86,21 @@
         IsChanging = True
         '开始实际获取
         RunInNewThread(
-        Async Sub()
+        Sub()
             Try
 Retry:
-                If McLoginMsLoader.State = LoadState.Loading Then McLoginMsLoader.WaitForExit() '等待登录结束
+                Do While McLoginMsLoader.State = LoadState.Loading '等待登录结束
+                    Thread.Sleep(10)
+                Loop
+                If McLoginMsLoader.State = LoadState.Failed Then Throw New Exception("登录失败", McLoginMsLoader.Error)
                 Dim AccessToken As String = Setup.Get("CacheMsV2Access")
                 Dim Uuid As String = Setup.Get("CacheMsV2Uuid")
-
-                Dim Client As New Net.Http.HttpClient With {.Timeout = New TimeSpan(0, 0, 30)}
-                Client.DefaultRequestHeaders.Authorization = New Net.Http.Headers.AuthenticationHeaderValue("Bearer", AccessToken)
-                Client.DefaultRequestHeaders.Accept.Add(New Net.Http.Headers.MediaTypeWithQualityHeaderValue("*/*"))
-                Client.DefaultRequestHeaders.UserAgent.Add(New Net.Http.Headers.ProductInfoHeaderValue("MojangSharp", "0.1"))
-                Dim Contents As New Net.Http.MultipartFormDataContent From {
-                    {New Net.Http.StringContent(If(SkinInfo.IsSlim, "slim", "classic")), "variant"},
-                    {New Net.Http.ByteArrayContent(ReadFileBytes(SkinInfo.LocalFile)), "file", GetFileNameFromPath(SkinInfo.LocalFile)}
-                }
-                Dim Result As String = Await (Await Client.PostAsync(New Uri("https://api.minecraftservices.com/minecraft/profile/skins"), Contents)).Content.ReadAsStringAsync
+                Dim Result As String = NetRequestByClientRetry("https://api.minecraftservices.com/minecraft/profile/skins", HttpMethod.Post,
+                    Content:=New Net.Http.MultipartFormDataContent From {
+                        {New Net.Http.StringContent(If(SkinInfo.IsSlim, "slim", "classic")), "variant"},
+                        {New Net.Http.ByteArrayContent(ReadFileBytes(SkinInfo.LocalFile)), "file", GetFileNameFromPath(SkinInfo.LocalFile)}
+                    },
+                    Headers:={{"Authorization", "Bearer " & AccessToken}, {"Accept", "*/*"}, {"User-Agent", "MojangSharp/0.1"}})
                 If Result.Contains("request requires user authentication") Then
                     Hint("正在登录,将在登录完成后继续更改皮肤……")
                     McLoginMsLoader.Start(GetLoginData(), IsForceRestart:=True)
@@ -122,8 +121,8 @@ Retry:
                 Next
                 Throw New Exception("未知错误(" & Result & ")")
             Catch ex As Exception
-                If ex.GetType.Equals(GetType(Tasks.TaskCanceledException)) Then
-                    Hint("更改皮肤失败:与 Mojang 皮肤服务器的连接超时,请检查你的网络是否通畅!", HintType.Critical)
+                If TypeOf ex Is OperationCanceledException Then
+                    Hint("更改皮肤失败:连接皮肤服务器超时,请稍后再试,或使用 VPN 改善网络环境", HintType.Critical)
                 Else
                     Log(ex, "更改皮肤失败", LogLevel.Hint)
                 End If

+ 493 - 452
Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml

@@ -1,452 +1,493 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    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="PageOtherAbout"
-    PanScroll="PanBack" RenderOptions.BitmapScalingMode="LowQuality">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel x:Name="PanMain" Margin="25,10" Grid.IsSharedSizeScope="True">
-            <local:MyCard Margin="0,15" Title="关于">
-                <Grid Margin="21,40,21,16">
-                    <Grid.RowDefinitions>
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                    </Grid.RowDefinitions>
-                    <Grid.ColumnDefinitions>
-                        <ColumnDefinition Width="40" />
-                        <ColumnDefinition />
-                        <ColumnDefinition Width="150" />
-                    </Grid.ColumnDefinitions>
-                    <local:MyListItem IsHitTestVisible="False" Title="龙腾猫跃" Margin="-5,0,15,0" Info="Plain Craft Launcher 的开发者!" Grid.Row="0" Grid.Column="1" />
-                    <local:MyListItem IsHitTestVisible="False" Title="Plain Craft Launcher 2" Margin="-5,0,15,0" Info="当前版本:%VERSION%(%VERSIONCODE%.%BRANCH%)" Grid.Row="1" Grid.Column="1" x:Name="ItemAboutPcl" />
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/Logo.png" Grid.Row="1" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/LTCat.jpg" Grid.Row="0" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <local:MyButton Text="赞助 PCL !" Grid.Column="2" Grid.Row="0" Height="35" EventType="打开网页" EventData="https://afdian.com/a/LTCat" ColorType="Highlight" />
-                    <local:MyButton Text="查看源代码" Grid.Column="2" Grid.Row="1" Height="35" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2" />
-                </Grid>
-            </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="特别鸣谢">
-                <Grid Margin="21,40,21,16">
-                    <Grid.RowDefinitions>
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                    </Grid.RowDefinitions>
-                    <Grid.ColumnDefinitions>
-                        <ColumnDefinition Width="40" />
-                        <ColumnDefinition />
-                        <ColumnDefinition Width="150" />
-                    </Grid.ColumnDefinitions>
-                    <local:MyListItem Title="bangbang93" Margin="-5,0,15,0" IsHitTestVisible="False" Info="提供 BMCLAPI 镜像源和 Forge 安装工具,详见 https://bmclapi.bangbang93.com" Grid.Row="0" Grid.Column="1" />
-                    <local:MyButton Text="赞助镜像源" Grid.Row="0" Grid.Column="2" Height="35" EventType="打开网页" EventData="https://afdian.com/a/bangbang93" />
-                    <local:MyListItem Title="MC 百科" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Mod 名称的中文翻译和更多相关信息!" Grid.Row="1" Grid.Column="1" />
-                    <local:MyButton Text="打开百科" Grid.Row="1" Grid.Column="2" Height="35" EventType="打开网页" EventData="https://www.mcmod.cn" />
-                    <local:MyListItem Title="z0z0r4" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 MCIM 社区资源镜像源和帮助库图床!" Grid.Row="2" Grid.Column="1" />
-                    <local:MyButton Text="MCIM 主页" Grid.Row="2" Grid.Column="2" Height="35" EventType="打开网页" EventData="https://github.com/mcmod-info-mirror" />
-                    <local:MyListItem Title="00ll00" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Java Launch Wrapper 和一些重要服务支持!" Grid.Row="3" Grid.Column="1" />
-                    <local:MyListItem Title="Patrick" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="设计并制作了 PCL 图标,让我从做图标的水深火热中得到了解脱……" Grid.Row="4" Grid.Column="1" />
-                    <local:MyListItem Title="Hao_Tian" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="在内测中找出了一大堆没人想得到的诡异 Bug,有非同寻常的 Bug 体质" Grid.Row="5" Grid.Column="1" />
-                    <local:MyListItem Title="Minecraft 中文论坛" Margin="-5,0,15,0" IsHitTestVisible="False" Info="虽然已经关站了,但感谢此前提供了 MCBBS 镜像源……" Grid.Row="6" Grid.Column="1" />
-                    <local:MyListItem Title="PCL 内群的各位" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="感谢内群的沙雕网友们这么久以来对我和 PCL 的支持与鼓励!" Grid.Row="7" Grid.Column="1" />
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/bangbang93.png" Grid.Row="0" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/wiki.png" Grid.Row="1" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/z0z0r4.png" Grid.Row="2" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/00ll00.png" Grid.Row="3" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/Patrick.png" Grid.Row="4" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/Hao_Tian.jpg" Grid.Row="5" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/MCBBS.png" Grid.Row="6" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/PCL2.png" Grid.Row="7" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                </Grid>
-            </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="赞助者">
-                <StackPanel Margin="25,40,10,20">
-                    <TextBlock HorizontalAlignment="Left" Text="以下是 2025 年 7 月的高级赞助者们!感谢各位对 PCL 和伟大的咕咕咕事业的支持!" Margin="0,0,25,5.5" TextTrimming="None" TextWrapping="WrapWithOverflow" />
-                    <WrapPanel Margin="0,0,0,10">
-                        <TextBlock Width="120" Margin="0,1.5" Text="ZSF" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="TizhleF" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="方源" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="日月空呵" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="贾明志" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="百瑞" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Flying___pig" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="绝世" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Jwac" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="万玖" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="墨忆" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="天刹" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Cess" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ender_glass" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="TT" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="白玄.雪狼" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="MUCT" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="崇昀" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="绪" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="吾皇万岁" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Ander·Les" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Eoctopus" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="时光飞逝一下午" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Carl7412" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ShinyChariot" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="骨故谷" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="?" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Jinji希子" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="自然选择" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="云念" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="钱尹鹏" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="TBONC" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="祈农" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="凤凰院凶真" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ShuangDaoRen" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="PXZ233" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="二十二今天有没有" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="天魕" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="南华清" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="66112447" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="夏行子" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="周俊成" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="乌漆嘛黑" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="肘击恶魔佐巴扬" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Pari~" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="红云" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Link-an" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="想要钱用不完" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="花宝" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="生于春天的雷电" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="被偷去的睡眠" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="liuxuan" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="寒枫凌" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="雀色时" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="架" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="终成究竟觉" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="S0D1UM" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="吞仔送命" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="白浪宇宙" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Crimson" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Mibo_MkIX" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="G.snine18^7" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="两把雨伞" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="古明地苑" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="MarbleMionshevic" />
-                        <TextBlock Width="120" Margin="0,1.5" Text=":〈" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="紫罗兰" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="asai" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="隔壁小明" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="就爱唱歌" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="沫神鹤" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="风已消逝" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="光意留闪" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="我们长征的路上" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="芸芷" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="函数极限" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="冰川泥" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="XunWei" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="sirous" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="路过某打酱油的" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Gnight07" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="结构德古拉" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="myyukee" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="云之彼端约定的地方" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="苏玖" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="赤沃净" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="YYC" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="晨曦酒庄高冷男" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="herald112" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Foxman" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="KoreQAQ" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="TechOfRevo" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="lqs" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="简某" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="源起于此" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="爱莉希雅" />
-                        <TextBlock Width="120" Margin="0,1.5" Text=" 和忧" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="全明星小面包批发商DIO" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="碎序" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="U" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Dumlet" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="johnqian" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="KANG" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="owod" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="九" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="苑煜" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="壹" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="UA-Ming" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="可爱小探姬" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="霜月" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="bud" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小虐SAIKYO" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="zealotand" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="comico" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="万山松" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="依梦明天" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="化形彧九尾" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="知崎" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="LOG175" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="绵" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="scp00813" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="宝宝" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="9527" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ICE_xwx" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="羽笙喵喵" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ssvgg(☉ x ☉)" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Happymeals" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="shen12244" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="一般路过AAA" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Niko" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="qroiwopqri" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="TringDron" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="有屳則名" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="TX2006" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="滑天下之大稽" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="中东悍匪" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="鬼泣々离殇" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="蒼崎青子" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="wyh114514" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="飞飞在睡觉。" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="柚西" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="XIAOSHUAI" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="saxioh" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="baibaibai" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="事屑Jerry" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Rs32" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="SPDish" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="风早九夜" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Reg" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="hellord" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="时本ki" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="mamana" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="放肆的微笑" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="人没逝" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="wddjww" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="xiao澄" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="银狼碎片" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="OniKata_Kayoko" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="曦" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Echo" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="画卿颜" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="夏 " />
-                        <TextBlock Width="120" Margin="0,1.5" Text="jiege45" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="甜食先生" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="虚莫妄玄" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="天恒" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="我有馄饨" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="布布曼" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="qwqewe" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="114514" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="YJ_42" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Xricron" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="抛弃大脑" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="10455885" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="羽渡白砂" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="沙" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="sukous" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="册那" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="KMYLS" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="kakadeso" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="懒猫" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="清岚" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Atopos" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="苦尽柑来" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="脑洞大开的血魔" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ももかわるこ" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="空中的风" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="黑夜幽灵" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="H1saNe" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="故风" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="臭氧化碳" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="云阿云" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小命" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="南宫若兮" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Landsilde" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="zzz" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="星期三" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Ashburn" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="123" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="九日" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="古神" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="月宫星耀" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="郁子恒是FW" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="每天吃吃吃吃吃不饱" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Freezhao" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ZHypocrite" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="十下小雨" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="九 十" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="RuthlessXdream" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="zv-vz" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="汐沄的随记" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="REMOTE" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="昔阳" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="七度ユキノ" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="KNISX" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="RainbowDashRD" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="夢曦" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="跳雨风" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="望言苏" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="四分白Fortenz" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="UUZ" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Ankairis" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Jn1E" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="云山如昨好" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="LYJ0415" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="空?" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Awfrlu+" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="懒帝" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="北安丶七叔" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="xkw" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="KINO" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="POTATO" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="少昊氏" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="狂踹瘸子那條好腿丶" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="Thallium_Ash【铊尘】(坨坨)" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="陌雨泠涯" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="bajie79" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="紘垰" />
-                    </WrapPanel>
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnDonateOutput" Grid.Column="0" MinWidth="140" Text="生成解锁码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" Visibility="Collapsed" />
-                        <local:MyButton x:Name="BtnDonateDonate" Grid.Column="0" MinWidth="140" Text="赞助作者" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" EventType="打开网页" EventData="https://afdian.com/a/LTCat" />
-                        <local:MyButton x:Name="BtnDonateCopy" Grid.Column="1" MinWidth="140" Text="复制识别码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
-                        <local:MyButton x:Name="BtnDonateInput" Grid.Column="2" MinWidth="140" Text="输入解锁码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="法律信息" IsSwapped="True" CanSwap="True">
-                <StackPanel Margin="25,39,23,20">
-                    <TextBlock Text="匿名数据收集" TextWrapping="Wrap" Margin="0,6,0,4" FontWeight="Bold" />
-                    <TextBlock Margin="0,0,0,2" LineHeight="17" Text="当下载失败、PCL 出错、游戏崩溃等事件时,PCL 会匿名上报信息,以通知龙猫 “哦吼,出错了”,让龙猫能更好地修 Bug……(PCL 不会上传或收集任何个人信息,包括上报的东西里面也没有!)&#xa;你可以在 设置 → 其他 → 启动器 中关闭上报,不过龙猫就不知道你遇到了什么 Bug 了……" TextWrapping="Wrap" />
-                    <TextBlock Text="隐私声明与个人信息保护政策" TextWrapping="Wrap" Margin="0,10,0,4" FontWeight="Bold" />
-                    <TextBlock Margin="0,0,0,2" LineHeight="17" Text="PCL 没有收集个人信息,所以这里其实啥都不用写……&#xa;留个这个其实只是为了让你知道 PCL 没有收集个人信息,诶嘿。" TextWrapping="Wrap" />
-                    <TextBlock Text="其他信息" TextWrapping="Wrap" Margin="0,10,0,4" FontWeight="Bold" />
-                    <TextBlock Margin="0,0,0,2" LineHeight="17" Text="Copyright © 龙腾猫跃 2016. All Rights Reserved.&#13;计算机软件著作权登记号:2020SR0875133&#13;违法违规行为举报投诉邮箱:[email protected]&#13;非 MINECRAFT 官方产品。未经 MOJANG 或 MICROSOFT 批准,也不与 MOJANG 或 MICROSOFT 关联。" TextWrapping="Wrap" />
-                    <StackPanel Orientation="Horizontal" Height="35" HorizontalAlignment="Left" Margin="0,12,0,0">
-                        <local:MyButton Text="用户协议与免责声明" Width="170" Padding="13,0" Margin="0,0,20,0" ColorType="Highlight" EventType="打开网页" EventData="https://shimo.im/docs/rGrd8pY8xWkt6ryW" />
-                        <local:MyButton Text="开源代码" Width="140" Padding="13,0" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2" />
-                    </StackPanel>
-                </StackPanel>
-            </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="许可与版权声明" IsSwapped="True" CanSwap="True">
-                <Grid Margin="25,42,23,4">
-                    <Grid.ColumnDefinitions>
-                        <ColumnDefinition Width="Auto" />
-                        <ColumnDefinition Width="20" />
-                        <ColumnDefinition Width="Auto" />
-                        <ColumnDefinition />
-                    </Grid.ColumnDefinitions>
-                    <Grid.RowDefinitions>
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                    </Grid.RowDefinitions>
-                    <TextBlock Grid.Row="0" Grid.Column="0" Text="Java Launch Wrapper" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2022 00ll00&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="1" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/00ll00/java_launch_wrapper" />
-                    <local:MyButton Grid.Row="1" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/00ll00/java_launch_wrapper?tab=MIT-1-ov-file#readme" />
-
-                    <TextBlock Grid.Row="2" Grid.Column="0" Text="Newtonsoft.Json" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2007 James Newton-King&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="3" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://www.newtonsoft.com/json" />
-                    <local:MyButton Grid.Row="3" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://opensource.org/licenses/MIT" />
-                    
-                    <TextBlock Grid.Row="4" Grid.Column="0" Text="NAudio" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2015 Mark Heath ([email protected])&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="5" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/naudio/NAudio" />
-                    <local:MyButton Grid.Row="5" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left" 
-                                    EventType="打开网页" EventData="https://github.com/naudio/NAudio/blob/master/license.txt" />
-                    
-                    <TextBlock Grid.Row="6" Grid.Column="0" Text="Ookii.Dialogs" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2009 Sven Groot (Ookii.org)" TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="7" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://www.ookii.org/Software/Dialogs/" />
-                    <local:MyButton Grid.Row="7" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/caioproiete/ookii-dialogs-wpf/blob/master/LICENSE" />
-                    
-                    <TextBlock Grid.Row="8" Grid.Column="0" Text="Iconmonstr" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2019 iconmonstr (Alexander Kahlkopf)" TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="9" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://iconmonstr.com/" />
-                    <local:MyButton Grid.Row="9" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://iconmonstr.com/license/" />
-                    
-                    <TextBlock Grid.Row="10" Grid.Column="0" Text="libwebp wrapper for .NET" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2012 Imazen&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="11" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/imazen/libwebp-net" />
-                    <local:MyButton Grid.Row="11" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/imazen/libwebp-net/blob/master/LICENSE.txt" />
-
-                    <TextBlock Grid.Row="12" Grid.Column="0" Text="Iconfont" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © ALIMAMA MUX, powered by alimama THX." TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="13" Grid.Column="2" MinWidth="140" Text="查看原网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://www.iconfont.cn/" />
-
-                </Grid>
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>
+<local:MyPageRight
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    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="PageOtherAbout"
+    PanScroll="PanBack" RenderOptions.BitmapScalingMode="LowQuality">
+    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
+        <StackPanel x:Name="PanMain" Margin="25,10" Grid.IsSharedSizeScope="True">
+            <local:MyCard Margin="0,15" Title="关于">
+                <Grid Margin="21,40,21,16">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="40" />
+                        <ColumnDefinition />
+                        <ColumnDefinition Width="150" />
+                    </Grid.ColumnDefinitions>
+                    <local:MyListItem IsHitTestVisible="False" Title="龙腾猫跃" Margin="-5,0,15,0" Info="Plain Craft Launcher 的开发者!" Grid.Row="0" Grid.Column="1" />
+                    <local:MyListItem IsHitTestVisible="False" Title="Plain Craft Launcher 2" Margin="-5,0,15,0" Info="当前版本:%VERSION%(%VERSIONCODE%.%BRANCH%)" Grid.Row="1" Grid.Column="1" x:Name="ItemAboutPcl" />
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/Logo.png" Grid.Row="1" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/LTCat.jpg" Grid.Row="0" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <local:MyButton Text="赞助 PCL !" Grid.Column="2" Grid.Row="0" Height="35" EventType="打开网页" EventData="https://afdian.com/a/LTCat" ColorType="Highlight" />
+                    <local:MyButton Text="查看源代码" Grid.Column="2" Grid.Row="1" Height="35" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2" />
+                </Grid>
+            </local:MyCard>
+            <local:MyCard Margin="0,0,0,15" Title="特别鸣谢">
+                <Grid Margin="21,40,21,16">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="40" />
+                        <ColumnDefinition />
+                        <ColumnDefinition Width="150" />
+                    </Grid.ColumnDefinitions>
+                    <local:MyListItem Title="bangbang93" Margin="-5,0,15,0" IsHitTestVisible="False" Info="提供 BMCLAPI 镜像源和 Forge 安装工具,详见 https://bmclapi.bangbang93.com" Grid.Row="0" Grid.Column="1" />
+                    <local:MyButton Text="赞助镜像源" Grid.Row="0" Grid.Column="2" Height="35" EventType="打开网页" EventData="https://afdian.com/a/bangbang93" />
+                    <local:MyListItem Title="MC 百科" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Mod 名称的中文翻译和更多相关信息!" Grid.Row="1" Grid.Column="1" />
+                    <local:MyButton Text="打开百科" Grid.Row="1" Grid.Column="2" Height="35" EventType="打开网页" EventData="https://www.mcmod.cn" />
+                    <local:MyListItem Title="z0z0r4" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 MCIM 社区资源镜像源和帮助库图床!" Grid.Row="2" Grid.Column="1" />
+                    <local:MyButton Text="MCIM 主页" Grid.Row="2" Grid.Column="2" Height="35" EventType="打开网页" EventData="https://github.com/mcmod-info-mirror" />
+                    <local:MyListItem Title="00ll00" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Java Launch Wrapper 和一些重要服务支持!" Grid.Row="3" Grid.Column="1" />
+                    <local:MyListItem Title="Patrick" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="设计并制作了 PCL 图标,让我从做图标的水深火热中得到了解脱……" Grid.Row="4" Grid.Column="1" />
+                    <local:MyListItem Title="Hao_Tian" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="在内测中找出了一大堆没人想得到的诡异 Bug,有非同寻常的 Bug 体质" Grid.Row="5" Grid.Column="1" />
+                    <local:MyListItem Title="Minecraft 中文论坛" Margin="-5,0,15,0" IsHitTestVisible="False" Info="虽然已经关站了,但感谢此前提供了 MCBBS 镜像源……" Grid.Row="6" Grid.Column="1" />
+                    <local:MyListItem Title="PCL 内群的各位" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="感谢内群的沙雕网友们这么久以来对我和 PCL 的支持与鼓励!" Grid.Row="7" Grid.Column="1" />
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/bangbang93.png" Grid.Row="0" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/wiki.png" Grid.Row="1" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/z0z0r4.png" Grid.Row="2" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/00ll00.png" Grid.Row="3" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/Patrick.png" Grid.Row="4" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/Hao_Tian.jpg" Grid.Row="5" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/MCBBS.png" Grid.Row="6" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/PCL2.png" Grid.Row="7" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                </Grid>
+            </local:MyCard>
+            <local:MyCard Margin="0,0,0,15" Title="赞助者">
+                <StackPanel Margin="25,40,10,20">
+                    <TextBlock HorizontalAlignment="Left" Text="以下是 2025 年 8 月的高级赞助者们!感谢各位对 PCL 和伟大的咕咕咕事业的支持!" Margin="0,0,25,5.5" TextTrimming="None" TextWrapping="WrapWithOverflow" />
+                    <WrapPanel Margin="0,0,0,10">
                        <TextBlock Width="120" Margin="0,1.5" Text="粒子鹰" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="暮色黄昏" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="未殃" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="咸鱼" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Claud_9527" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Qqsnd" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="☆Kiana☆" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="晓枫-橘味C" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Ferderica" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="过路人" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="寒溪" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="小咪vv" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="容小狸" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="南宫流溪" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="懒包子" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="王家有政" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="??????" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="念念" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="启233" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="爱小黎" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="北笙T-T" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="listpmater" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="朝朝超级の好运" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="低调" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="老公" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="雍仕" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="AAboss" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="闲来无事" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="omgddcsl" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="轻舟" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="浮生Yui" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="YangFK" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="一曲勺" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="时白Herhilo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="寒冬落雨" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="柯基0v0" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="月兔" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="qkowo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="沐白" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="每雨天" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="AiNo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="木叶飞舞之处" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="wilbert_laogao" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="悠芸" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="黑白盗贼" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="PotCatawa" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="小王" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="花还没开" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="PixelFrontier像素边界" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="坊之岬" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="HuTu" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="爱言叶ray" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="lenrin" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="." />
+                        <TextBlock Width="120" Margin="0,1.5" Text="这是一头灯笼花" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="肖然星辰" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="QMX" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="山外诗" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="刘" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="萧风溪水" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="风落" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="星星绒绒" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Ciallo~(∠?ω" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="九十九" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Guuzen" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="9331" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="钱程" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="杏坂和人" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="jinlijsbjaj" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="海豹" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="洛玉衡kale" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="柠乔" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="N0NiL" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="阿卡零" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ADAA" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="岚瑬" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="爱料理的猫" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="01B8" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="MCDJ喜羊羊" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="hwch55" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="棉花糖十五" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="鱼头" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="拉普兰德天下第一" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="KGIKUY" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="初洛" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="z .e.r.o" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="独孤真菜" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="阿囊" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="rrrwcsza" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="守卫军的杂鱼兵" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="念安" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="叶" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="rreorr" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="星辰大海" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="随便" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="烤着吃的鱼" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="大概是" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="闹山麻雀" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="我是(⊙o⊙)啥?笔" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="北纬44" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="呼呼呼" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="嘎嘎不咕咕" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="kronos" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="有个好梦,同志" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Amojun" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="bat-744" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="那谁" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="AYOSO" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="抽抽" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="柚子皮皮皮" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="白玉京" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ALPSL" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="我爱喝兽奶" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="wwq557" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="爱莉希雅" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="上善若水" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="zztzzy" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="SHiao_Yui" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="一只花生" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="黑凤梨熊?" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="sdad" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="BLONG" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="787878" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="myry668" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="f三文鱼f" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="apqow" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="懒梦棉花糖" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="HaiMing" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Evernight" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="CieloNeet" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="不青盆" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Purelimiter" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="丨白丨" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="喵" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="雨过山秋" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="急了" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="歌斐木" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="公子天邪" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="爱发癫" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="洪某某" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="漫卡洛樱" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="云与海" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="子曰" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="CyrilZ" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="笑颜如花兮" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="咸鱼超人FF酱!" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="达达利鸭" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="TFofN" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Kumo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="江俞白" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="聆风忘年" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="五符临门" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="亭书浅墨" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="翀归滉" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Anmuer" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="青天揽明月" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="鲫鱼崽" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="PPTNPC" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="vIccvI" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="GOOD" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="归零之诗" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ppp" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Ethanhmm1234" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="库里斯库拉" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="被伪安全软件坑害的普通人" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="无元亓" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Raven" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="看看你的jjp" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="日月皆如水" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="gzwmmm" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="早上鸥" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="CJT" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="幽永森" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="东海帝皇" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="三岁" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="shui12dkhigl" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="豪猪" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="再相遇" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="?白樱Hakura" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="IssacPyromaniac" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="纯真丁一郎" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="岩之龙" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="星之金白" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="laocao" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="DLC...是什么" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="我家老米" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="TINY" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="只会coding的农民工" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="明月一行" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="nanako" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="辛霏Aiwx" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="奇妙的白给之旅" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="你咋不上天呢^_^" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="冰糕" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="sadas" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="小叮当搞快点" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="leu" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="休伯利安本来的舰长" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="天诺一字号仓鼠" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="真爱粉只因龙" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="绝望的温度" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="busituteng" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="SiC" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="契特卡拉概念机" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="我还能再在死线挣扎一会" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="02_G1nTonic" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="渊" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="1" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="小楠" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="阿冰" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="EliasG" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="jhx" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="zyccmyk" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="luxin" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="我是我" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="13462738215" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="minecraft java" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="那是小林吖" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="GITATIG" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Musketeer" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="AuraSpeedJerry" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="luckymore" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="hcrmhj" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="我好焦灼啊" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="小王同学" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="MoDust2131" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="啊超" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Ander·Les" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Niko" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="五个麦卷" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="b0Ard" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="龙猫的专属粉丝" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="雾念~" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="烬灵梦" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="蝶落染花香" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="照井黎Terui_Rei" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="???狼??王?" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Zik yeung" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Coco" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Ricardo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="2969307002" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="鬼卿" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="李白" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="霜木翔凤" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="岽龙" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="joksqp" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="what are you 说啥嘞?" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="木得爱_没人疼" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="小黑子?" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="删除线Strikethrough" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="REMOTE" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="H" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Parapha" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="南吕十三" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="米帝" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="zdxff" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="蔚空" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="cleverboy1066" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="烤老鼠" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="TS彦祖" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="狮某人" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="lmst_myh" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="耐·若何" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="qvq" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="刀客塔" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="mika" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="金尘" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="喜之狼与灰太羊" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="天才小宋" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="MdsR" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="诗和雨夜" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Samueldong" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="BBK" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="雪枫Sople" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="一坨灰" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="RI_CK" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ximeng22" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="天天122_" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="李小浩&amp;mod" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Southsheng" />
+                    </WrapPanel>
+                    <Grid Height="35">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                        </Grid.ColumnDefinitions>
+                        <local:MyButton x:Name="BtnDonateOutput" Grid.Column="0" MinWidth="140" Text="生成解锁码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" Visibility="Collapsed" />
+                        <local:MyButton x:Name="BtnDonateDonate" Grid.Column="0" MinWidth="140" Text="赞助作者" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" EventType="打开网页" EventData="https://afdian.com/a/LTCat" />
+                        <local:MyButton x:Name="BtnDonateCopy" Grid.Column="1" MinWidth="140" Text="复制识别码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
+                        <local:MyButton x:Name="BtnDonateInput" Grid.Column="2" MinWidth="140" Text="输入解锁码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
+                    </Grid>
+                </StackPanel>
+            </local:MyCard>
+            <local:MyCard Margin="0,0,0,15" Title="法律信息" IsSwapped="True" CanSwap="True">
+                <StackPanel Margin="25,39,23,20">
+                    <TextBlock Text="匿名数据收集" TextWrapping="Wrap" Margin="0,6,0,4" FontWeight="Bold" />
+                    <TextBlock Margin="0,0,0,2" LineHeight="17" Text="当下载失败、PCL 出错、游戏崩溃等事件时,PCL 会匿名上报信息,以通知龙猫 “哦吼,出错了”,让龙猫能更好地修 Bug……(PCL 不会上传或收集任何个人信息,包括上报的东西里面也没有!)&#xa;你可以在 设置 → 其他 → 启动器 中关闭上报,不过龙猫就不知道你遇到了什么 Bug 了……&#xa;详情:https://github.com/Meloong-Git/PCL/discussions/7036" TextWrapping="Wrap" />
+                    <TextBlock Text="隐私声明与个人信息保护政策" TextWrapping="Wrap" Margin="0,10,0,4" FontWeight="Bold" />
+                    <TextBlock Margin="0,0,0,2" LineHeight="17" Text="PCL 没有收集个人信息,所以这里其实啥都不用写……&#xa;留个这个其实只是为了让你知道 PCL 没有收集个人信息,诶嘿。" TextWrapping="Wrap" />
+                    <TextBlock Text="其他信息" TextWrapping="Wrap" Margin="0,10,0,4" FontWeight="Bold" />
+                    <TextBlock Margin="0,0,0,2" LineHeight="17" Text="Copyright © 龙腾猫跃 2016. All Rights Reserved.&#13;计算机软件著作权登记号:2020SR0875133&#13;违法违规行为举报投诉邮箱:[email protected]&#13;非 MINECRAFT 官方产品。未经 MOJANG 或 MICROSOFT 批准,也不与 MOJANG 或 MICROSOFT 关联。" TextWrapping="Wrap" />
+                    <StackPanel Orientation="Horizontal" Height="35" HorizontalAlignment="Left" Margin="0,12,0,0">
+                        <local:MyButton Text="用户协议与免责声明" Width="170" Padding="13,0" Margin="0,0,20,0" ColorType="Highlight" EventType="打开网页" EventData="https://shimo.im/docs/rGrd8pY8xWkt6ryW" />
+                        <local:MyButton Text="开源代码" Width="140" Padding="13,0" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2" />
+                    </StackPanel>
+                </StackPanel>
+            </local:MyCard>
+            <local:MyCard Margin="0,0,0,15" Title="许可与版权声明" IsSwapped="True" CanSwap="True">
+                <Grid Margin="25,42,23,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto" />
+                        <ColumnDefinition Width="20" />
+                        <ColumnDefinition Width="Auto" />
+                        <ColumnDefinition />
+                    </Grid.ColumnDefinitions>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                    </Grid.RowDefinitions>
+                    <TextBlock Grid.Row="0" Grid.Column="0" Text="Java Launch Wrapper" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2022 00ll00&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="1" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://github.com/00ll00/java_launch_wrapper" />
+                    <local:MyButton Grid.Row="1" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://github.com/00ll00/java_launch_wrapper?tab=MIT-1-ov-file#readme" />
+
+                    <TextBlock Grid.Row="2" Grid.Column="0" Text="Newtonsoft.Json" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2007 James Newton-King&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="3" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://www.newtonsoft.com/json" />
+                    <local:MyButton Grid.Row="3" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://opensource.org/licenses/MIT" />
+                    
+                    <TextBlock Grid.Row="4" Grid.Column="0" Text="NAudio" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2015 Mark Heath ([email protected])&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="5" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://github.com/naudio/NAudio" />
+                    <local:MyButton Grid.Row="5" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left" 
+                                    EventType="打开网页" EventData="https://github.com/naudio/NAudio/blob/master/license.txt" />
+                    
+                    <TextBlock Grid.Row="6" Grid.Column="0" Text="Ookii.Dialogs" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2009 Sven Groot (Ookii.org)" TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="7" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://www.ookii.org/Software/Dialogs/" />
+                    <local:MyButton Grid.Row="7" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://github.com/caioproiete/ookii-dialogs-wpf/blob/master/LICENSE" />
+                    
+                    <TextBlock Grid.Row="8" Grid.Column="0" Text="Iconmonstr" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2019 iconmonstr (Alexander Kahlkopf)" TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="9" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://iconmonstr.com/" />
+                    <local:MyButton Grid.Row="9" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://iconmonstr.com/license/" />
+                    
+                    <TextBlock Grid.Row="10" Grid.Column="0" Text="libwebp wrapper for .NET" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2012 Imazen&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="11" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://github.com/imazen/libwebp-net" />
+                    <local:MyButton Grid.Row="11" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://github.com/imazen/libwebp-net/blob/master/LICENSE.txt" />
+
+                    <TextBlock Grid.Row="12" Grid.Column="0" Text="Iconfont" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © ALIMAMA MUX, powered by alimama THX." TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="13" Grid.Column="2" MinWidth="140" Text="查看原网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://www.iconfont.cn/" />
+
+                </Grid>
+            </local:MyCard>
+        </StackPanel>
+    </local:MyScrollViewer>
+</local:MyPageRight>

+ 2 - 2
Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb

@@ -132,7 +132,7 @@
     End Sub
     Public Shared Sub TryFeedback()
         If Not CanFeedback(True) Then Return
-        Select Case MyMsgBox("在提交新反馈前,建议先搜索反馈列表,以避免重复提交。" & vbCrLf & "如果无法打开该网页,请使用 VPN 以改善网络环境。",
+        Select Case MyMsgBox("在提交新反馈前,建议先搜索反馈列表,以避免重复提交。" & vbCrLf & "如果无法打开该网页,请使用 VPN 改善网络环境。",
                     "反馈", "提交新反馈", "查看反馈列表", "取消")
             Case 1
                 Feedback(True, False)
@@ -146,7 +146,7 @@
         e.Handled = True
     End Sub
     Public Shared Sub TryVote()
-        If MyMsgBox("是否要打开新功能投票网页?" & vbCrLf & "如果无法打开该网页,请使用 VPN 以改善网络环境。",
+        If MyMsgBox("是否要打开新功能投票网页?" & vbCrLf & "如果无法打开该网页,请使用 VPN 改善网络环境。",
                     "新功能投票", "打开", "取消") = 2 Then Return
         OpenWebsite("https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8?discussions_q=category%3A%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8+sort%3Adate_created")
     End Sub

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

@@ -317,9 +317,6 @@
     Public Sub Delete_Click(sender As Object, e As RoutedEventArgs)
         Dim Folder As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag
         Dim DeleteText As String = If((Folder.Type = McFolderType.Original OrElse Folder.Type = McFolderType.RenamedOriginal) AndAlso Folder.Path = Path & ".minecraft\" AndAlso McFolderList.Count = 1, "清空", "删除")
-        If Folder.Path.EndsWith(":\") OrElse Folder.Path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) & "\" OrElse Folder.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\" Then '#7030
-            If MyMsgBox("将要删除的游戏文件夹是系统关键目录(" & Folder.Path & "),确定要删除吗?", "删除警告", "取消", "确认", "取消", IsWarn:=True) <> 2 Then Return
-        End If
         If MyMsgBox("你确定要" & DeleteText & "这个文件夹吗?" & vbCrLf & "目标文件夹:" & Folder.Path & vbCrLf & vbCrLf & "这会导致该文件夹中的所有存档与其他文件永久丢失,且不可恢复!", "删除警告", "取消", "确认", "取消") <> 2 Then Return
         If MyMsgBox("如果你在该文件夹中存放了除 MC 以外的其他文件,这些文件也会被一同删除!" & vbCrLf & "继续删除会导致该文件夹中的所有文件永久丢失,请在仔细确认后再继续!" & vbCrLf & "目标文件夹:" & Folder.Path & vbCrLf & vbCrLf & "这是最后一次警告!", "删除警告", "确认" & DeleteText, "取消", IsWarn:=True) <> 1 Then Return
         '移出列表

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

@@ -218,7 +218,7 @@
                     </Grid>
                 </StackPanel>
             </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="高级启动选项" IsSwapped="True" CanSwap="True">
+            <local:MyCard Margin="0,0,0,15" Title="高级选项" IsSwapped="True" CanSwap="True">
                 <Grid Margin="25,40,25,15">
                     <Grid.ColumnDefinitions>
                         <ColumnDefinition Width="Auto" />
@@ -255,7 +255,7 @@
                         <local:MyCheckBox Height="28" Text="要求 Java 使用高性能显卡" x:Name="CheckAdvanceGraphicCard" Tag="LaunchAdvanceGraphicCard"
                             ToolTipService.Placement="Right" ToolTip="自动在 Windows 设置 → 系统 → 屏幕 → 显示卡 中将 Java 改为使用高性能显卡。&#xa;如果电脑同时有集显和独显,这能确保 MC 使用独显进行渲染,以大幅提高性能。" />
                     </StackPanel>
-                    <local:MyHint Text="你还可以在版本独立设置中找到更多高级启动选项。" Margin="0,5,0,10" Theme="Blue" Grid.Row="7" Grid.ColumnSpan="2"
+                    <local:MyHint Text="版本独立设置中还有更多高级选项可供调整。" Margin="0,5,0,10" Theme="Blue" Grid.Row="7" Grid.ColumnSpan="2"
                                   CanClose="True" RelativeSetup="HintMoreAdvancedSetup" />
                 </Grid>
             </local:MyCard>

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

@@ -71,7 +71,7 @@
         Try
             Setup.Reset("LaunchArgumentTitle")
             Setup.Reset("LaunchArgumentInfo")
-            Setup.Reset("LaunchArgumentIndieV2")
+            Setup.Set("LaunchArgumentIndieV2", Setup.GetDefault("LaunchArgumentIndieV2"))
             Setup.Reset("LaunchArgumentVisible")
             Setup.Reset("LaunchArgumentWindowType")
             Setup.Reset("LaunchArgumentWindowWidth")
@@ -585,4 +585,12 @@ PreFin:
         FrmMain.PageChange(FormMain.PageType.VersionSetup, FormMain.PageSubType.VersionSetup)
     End Sub
 
+    '去除参数中的回车
+    Private Sub ReplaceEnter(sender As MyTextBox, e As TextChangedEventArgs) Handles TextAdvanceJvm.TextChanged, TextAdvanceGame.TextChanged
+        Dim NewText = sender.Text.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, " ")
+        If NewText = sender.Text Then Return
+        Dim CaretIndex = sender.CaretIndex
+        sender.Text = NewText
+        sender.CaretIndex = Math.Max(0, CaretIndex - 1)
+    End Sub
 End Class

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

@@ -150,7 +150,7 @@
                     </local:MyTextBox>
                     <local:MyCheckBox Margin="0,5,0,4" Text="匿名数据收集" Grid.Row="6" Height="22" Grid.ColumnSpan="2"
                         x:Name="CheckSystemTelemetry" Tag="SystemSystemTelemetry" 
-                        ToolTip="当下载失败、PCL 出错、游戏崩溃等事件时,PCL 会匿名上报情况,以通知龙猫 “哦吼,出错了”,让龙猫能更好地修 Bug……&#xa;你可以关闭上报,不过龙猫就不知道你遇到了什么 Bug 了……&#13;PCL 不会上传或收集任何个人信息,包括上报的东西里面也没有!" />
+                        ToolTip="当下载失败、PCL 出错、游戏崩溃等事件时,PCL 会匿名上报情况,以通知龙猫 “哦吼,出错了”,让龙猫能更好地修 Bug……&#xa;你可以关闭上报,不过龙猫就不知道你遇到了什么 Bug 了……&#13;PCL 不会上传或收集任何个人信息,包括上报的东西里面也没有!&#xa;详情:https://github.com/Meloong-Git/PCL/discussions/7036" />
                     <Grid Height="35" Grid.Row="7" Grid.ColumnSpan="5" Margin="0,12,0,0">
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />

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

@@ -92,7 +92,7 @@ Public Class PageSpeedLeft
                             Card.Children.Clear()
                             Card.Children.Add(GetObjectFromXML("<Path xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" Stretch=""Uniform"" Tag=""Failed"" Data=""F1 M2.5,0 L0,2.5 7.5,10 0,17.5 2.5,20 10,12.5 17.5,20 20,17.5 12.5,10 20,2.5 17.5,0 10,7.5 2.5,0Z"" Height=""15"" Width=""15"" HorizontalAlignment=""Center"" Grid.Column=""0"" Grid.Row=""0"" Fill=""{DynamicResource ColorBrush3}"" Margin=""0,1,0,0"" VerticalAlignment=""Top""/>"))
                             Dim Tb As TextBlock = GetObjectFromXML("<TextBlock xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" TextWrapping=""Wrap"" HorizontalAlignment=""Left"" ToolTip=""单击复制错误详情"" Grid.Column=""1"" Grid.Row=""0"" Margin=""0,0,0,5"" />")
-                            Tb.Text = GetExceptionDetail(Loader.Error)
+                            Tb.Text = Loader.Error.GetDetail()
                             AddHandler Tb.MouseLeftButtonDown,
                             Sub(sender As TextBlock, e As EventArgs)
                                 ClipboardSet(sender.Text, False)

+ 8 - 8
Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml

@@ -106,6 +106,14 @@
                                 DefaultChecked="True" />
                                 </local:MyCheckBox.Tag>
                             </local:MyCheckBox>
+                            <local:MyCheckBox>
+                                <local:MyCheckBox.Tag>
+                                    <local:ExportOption 
+                                Title="TaCZ 枪包"
+                                Rules="tacz/|config/tacz/custom/"
+                                DefaultChecked="True" />
+                                </local:MyCheckBox.Tag>
+                            </local:MyCheckBox>
                             <local:MyCheckBox>
                                 <local:MyCheckBox.Tag>
                                     <local:ExportOption 
@@ -133,14 +141,6 @@
                                 DefaultChecked="False" />
                                 </local:MyCheckBox.Tag>
                             </local:MyCheckBox>
-                            <local:MyCheckBox>
-                                <local:MyCheckBox.Tag>
-                                    <local:ExportOption 
-                                Title="TaCZ 枪包"
-                                Rules="tacz/|config/tacz/custom/"
-                                DefaultChecked="False" />
-                                </local:MyCheckBox.Tag>
-                            </local:MyCheckBox>
                             <local:MyCheckBox>
                                 <local:MyCheckBox.Tag>
                                     <local:ExportOption 

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

@@ -418,7 +418,7 @@ Public Class PageVersionExport
                 Log($"[Export] 使用配置文件中指定的导出路径:{ConfigPackPath}")
             Catch ex As Exception
                 Log(ex, $"无法使用配置文件中指定的导出路径({ConfigPackPath})", LogLevel.Debug)
-                If MyMsgBox($"指定的路径:{ConfigPackPath}{vbCrLf}{vbCrLf}{GetExceptionDetail(ex)}", "无法使用配置文件中指定的导出路径", "确定", "取消") = 2 Then Return
+                If MyMsgBox($"指定的路径:{ConfigPackPath}{vbCrLf}{vbCrLf}{ex.GetDetail}", "无法使用配置文件中指定的导出路径", "确定", "取消") = 2 Then Return
             End Try
         End If
         If PackPath Is Nothing Then
@@ -530,6 +530,7 @@ Public Class PageVersionExport
             Loader.Progress = 0.97
             '复制 PCL 版本设置
             CopyDirectory(McVersion.Path & "PCL\", OverridesFolder & "PCL\")
+            WriteIni(OverridesFolder & "PCL\Setup.ini", "IsStar", False)
 #If BETA Then
             '复制 PCL 本体
             If IncludePCL Then CopyFile(PathWithName, CacheFolder & "Plain Craft Launcher.exe")

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

@@ -681,7 +681,7 @@ Install:
                                 Hint($"已成功更新 {FinishedFileNames.Count} 个 Mod!", HintType.Finish)
                         End Select
                     Case LoadState.Failed
-                        Hint("Mod 更新失败:" & GetExceptionSummary(Loader.Error), HintType.Critical)
+                        Hint("Mod 更新失败:" & Loader.Error.GetBrief(), HintType.Critical)
                     Case LoadState.Aborted
                         Hint("Mod 更新已中止!", HintType.Info)
                     Case Else
@@ -808,7 +808,7 @@ Install:
             Dim ModEntry As McMod = CType(If(TypeOf sender Is MyIconButton, sender.Tag, sender), MyLocalModItem).Entry
             '加载失败信息
             If ModEntry.State = McMod.McModState.Unavailable Then
-                MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & GetExceptionDetail(ModEntry.FileUnavailableReason), "Mod 读取失败")
+                MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & ModEntry.FileUnavailableReason.GetDetail(), "Mod 读取失败")
                 Return
             End If
             If ModEntry.Comp IsNot Nothing Then

+ 10 - 46
Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb

@@ -109,25 +109,23 @@
             Dim OldName As String = PageVersionLeft.Version.Name
             Dim OldPath As String = PageVersionLeft.Version.Path
             '修改此部分的同时修改快速安装的版本名检测*
-            Dim NewName As String = MyMsgBoxInput("重命名版本", "", OldName, New ObjectModel.Collection(Of Validate) From {New ValidateFolderName(PathMcFolder & "versions", IgnoreCase:=False)})
+            Dim NewName As String = MyMsgBoxInput("重命名版本", "", OldName, New ObjectModel.Collection(Of Validate) From {
+                New ValidateFolderName(PathMcFolder & "versions", IgnoreCase:=False), New ValidateExceptSame(OldName & "_temp", "不能使用该名称!")})
             If String.IsNullOrWhiteSpace(NewName) Then Return
             Dim NewPath As String = PathMcFolder & "versions\" & NewName & "\"
             '获取临时中间名,以防止仅修改大小写的重命名失败
             Dim TempName As String = NewName & "_temp"
             Dim TempPath As String = PathMcFolder & "versions\" & TempName & "\"
-            Dim IsCaseChangedOnly As Boolean = NewName.ToLower = OldName.ToLower
-            
-            '读取版本 JSON 信息
+            Dim OnlyChangedCase As Boolean = NewName.ToLower = OldName.ToLower
+            '重新读取版本 JSON 信息,避免 JsonObject 中已被合并的项被重新存储
             Dim JsonObject As JObject
-            Dim OldJsonPath As String = ""
+            Dim OldJsonPath As String = PageVersionLeft.Version.GetJsonPath()
             Try
-                OldJsonPath = PageVersionLeft.Version.GetJsonPath()
                 JsonObject = GetJson(ReadFile(OldJsonPath))
             Catch ex As Exception
                 Log(ex, "在重命名读取 json 时失败")
                 JsonObject = PageVersionLeft.Version.JsonObject
             End Try
-            
             '重命名主文件夹
             My.Computer.FileSystem.RenameDirectory(OldPath, TempName)
             My.Computer.FileSystem.RenameDirectory(TempPath, NewName)
@@ -137,7 +135,7 @@
             '重命名 jar 文件与 natives 文件夹
             '不能进行遍历重命名,否则在版本名很短的时候容易误伤其他文件(#6443)
             If Directory.Exists($"{NewPath}{OldName}-natives") Then
-                If IsCaseChangedOnly Then
+                If OnlyChangedCase Then
                     My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives", $"{OldName}natives_temp")
                     My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives_temp", $"{NewName}-natives")
                 Else
@@ -146,7 +144,7 @@
                 End If
             End If
             If File.Exists($"{NewPath}{OldName}.jar") Then
-                If IsCaseChangedOnly Then
+                If OnlyChangedCase Then
                     My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}.jar", $"{OldName}_temp.jar")
                     My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}_temp.jar", $"{NewName}.jar")
                 Else
@@ -162,48 +160,14 @@
             If ReadIni(PathMcFolder & "PCL.ini", "Version") = OldName Then
                 WriteIni(PathMcFolder & "PCL.ini", "Version", NewName)
             End If
-            
-            '处理JSON文件:先写入新文件,再删除旧文件
+            '更新版本 Json
             Try
-                '更新JSON内容中的id
                 JsonObject("id") = NewName
-                '写入新的JSON文件
                 WriteFile(NewPath & NewName & ".json", JsonObject.ToString)
-                
-                '删除旧的JSON文件(现在在新路径下)
-                Dim OldJsonInNewPath As String = NewPath & OldName & ".json"
-                If File.Exists(OldJsonInNewPath) Then
-                    File.Delete(OldJsonInNewPath)
-                End If
+                File.Delete(NewPath & GetFileNameFromPath(OldJsonPath))
             Catch ex As Exception
                 Log(ex, "重命名版本 json 失败")
-                '如果JSON处理失败,尝试回滚整个操作
-                If Directory.Exists(NewPath) AndAlso Not Directory.Exists(OldPath) Then
-                    Try
-                        '还原jar文件名
-                        If File.Exists($"{NewPath}{NewName}.jar") Then
-                            My.Computer.FileSystem.RenameFile($"{NewPath}{NewName}.jar", $"{OldName}.jar")
-                        End If
-                        '还原natives文件夹名
-                        If Directory.Exists($"{NewPath}{NewName}-natives") Then
-                            My.Computer.FileSystem.RenameDirectory($"{NewPath}{NewName}-natives", $"{OldName}-natives")
-                        End If
-                    Catch
-                        ' 忽略 jar 文件, natives 文件夹还原失败
-                    End Try
-                End If
-                Try
-                    '还原文件夹名
-                    My.Computer.FileSystem.RenameDirectory(NewPath, OldName)
-                    Hint("重命名失败:无法处理版本配置文件,已回滚更改", HintType.Critical)
-                    Return
-                Catch rollbackEx As Exception
-                    Log(rollbackEx, "重命名失败后尝试回滚也失败")
-                    Hint("重命名失败:版本文件可能已损坏,请检查版本文件夹", HintType.Critical)
-                    Return
-                End Try
             End Try
-            
             '刷新与提示
             Hint("重命名成功!", HintType.Finish)
             PageVersionLeft.Version = New McVersion(NewName).Load()
@@ -337,7 +301,7 @@
                     Case LoadState.Finished
                         Hint(Loader.Name & "成功!", HintType.Finish)
                     Case LoadState.Failed
-                        Hint(Loader.Name & "失败:" & GetExceptionSummary(Loader.Error), HintType.Critical)
+                        Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical)
                     Case LoadState.Aborted
                         Hint(Loader.Name & "已取消!", HintType.Info)
                 End Select

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

@@ -192,7 +192,7 @@
                     <local:MyButton x:Name="BtnServerAuthLittle" Height="35" MinWidth="150" Margin="0,12,0,0" Text="设置为 LittleSkin" HorizontalAlignment="Left" ColorType="Highlight" />
                 </StackPanel>
             </local:MyCard>
-            <local:MyCard x:Name="CardAdvance" Margin="0,0,0,15" Title="高级启动选项" IsSwapped="True" CanSwap="True">
+            <local:MyCard x:Name="CardAdvance" Margin="0,0,0,15" Title="高级选项" IsSwapped="True" CanSwap="True">
                 <Grid Margin="25,40,25,15">
                     <Grid.ColumnDefinitions>
                         <ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
@@ -219,7 +219,8 @@
                     <local:MyCheckBox Grid.Row="5" Grid.Column="1" x:Name="CheckAdvanceRunWait" Tag="VersionAdvanceRunWait" Visibility="Collapsed"
                                       Text="等待命令执行完成后再继续启动" Margin="0,5,0,0" />
                     <StackPanel Margin="0,12,0,4" Grid.Row="6" Grid.ColumnSpan="2" HorizontalAlignment="Left">
-                        <local:MyCheckBox Height="28" Text="禁止 Mod 更新" x:Name="CheckAdvanceDisableModUpdate" Tag="VersionAdvanceDisableModUpdate"
+                        <local:MyCheckBox Height="28" Text="禁止更新 Mod" x:Name="CheckAdvanceDisableModUpdate" Tag="VersionAdvanceDisableModUpdate"
+                                          ToolTip="禁止为此版本更新 Mod,以防止整合包玩家误操作"
                                           ToolTipService.Placement="Right" />
                         <local:MyCheckBox Height="28" Text="忽略 Java 兼容性警告" x:Name="CheckAdvanceJava" Tag="VersionAdvanceJava"
                                           ToolTipService.Placement="Right"

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

@@ -517,4 +517,12 @@ PreFin:
         FrmMain.PageChange(FormMain.PageType.Setup, FormMain.PageSubType.SetupLaunch)
     End Sub
 
+    '去除参数中的回车
+    Private Sub ReplaceEnter(sender As MyTextBox, e As TextChangedEventArgs) Handles TextAdvanceJvm.TextChanged, TextAdvanceGame.TextChanged
+        Dim NewText = sender.Text.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, " ")
+        If NewText = sender.Text Then Return
+        Dim CaretIndex = sender.CaretIndex
+        sender.Text = NewText
+        sender.CaretIndex = Math.Max(0, CaretIndex - 1)
+    End Sub
 End Class

+ 1 - 42
Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj

@@ -207,6 +207,7 @@
     </ApplicationDefinition>
     <Compile Include="Controls\Behaviors\ClipboardInterceptor.vb" />
     <Compile Include="Controls\IMyRadio.vb" />
+    <Compile Include="Controls\Interfaces.vb" />
     <Compile Include="Controls\MyComboBox.vb" />
     <Compile Include="Controls\MyComboBoxItem.vb" />
     <Compile Include="Controls\MyCard.vb" />
@@ -282,18 +283,12 @@
     <Compile Include="Pages\PageDownload\Comp\PageDownloadMod.xaml.vb">
       <DependentUpon>PageDownloadMod.xaml</DependentUpon>
     </Compile>
-    <Compile Include="Pages\PageDownload\PageDownloadFabric.xaml.vb">
-      <DependentUpon>PageDownloadFabric.xaml</DependentUpon>
-    </Compile>
     <Compile Include="Pages\PageDownload\PageDownloadInstall.xaml.vb">
       <DependentUpon>PageDownloadInstall.xaml</DependentUpon>
     </Compile>
     <Compile Include="Pages\PageDownload\Comp\PageDownloadCompDetail.xaml.vb">
       <DependentUpon>PageDownloadCompDetail.xaml</DependentUpon>
     </Compile>
-    <Compile Include="Pages\PageDownload\PageDownloadNeoForge.xaml.vb">
-      <DependentUpon>PageDownloadNeoForge.xaml</DependentUpon>
-    </Compile>
     <Compile Include="Pages\PageLaunch\MyMsgLogin.xaml.vb">
       <DependentUpon>MyMsgLogin.xaml</DependentUpon>
     </Compile>
@@ -352,15 +347,6 @@
     <Compile Include="Modules\Minecraft\ModDownload.vb" />
     <Compile Include="Modules\Minecraft\ModLaunch.vb" />
     <Compile Include="Modules\Minecraft\ModMinecraft.vb" />
-    <Compile Include="Pages\PageDownload\PageDownloadForge.xaml.vb">
-      <DependentUpon>PageDownloadForge.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\PageDownload\PageDownloadLiteLoader.xaml.vb">
-      <DependentUpon>PageDownloadLiteLoader.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\PageDownload\PageDownloadOptiFine.xaml.vb">
-      <DependentUpon>PageDownloadOptiFine.xaml</DependentUpon>
-    </Compile>
     <Compile Include="Pages\PageOther\PageOtherTest.xaml.vb">
       <DependentUpon>PageOtherTest.xaml</DependentUpon>
     </Compile>
@@ -394,9 +380,6 @@
     <Compile Include="Pages\PageDownload\PageDownloadLeft.xaml.vb">
       <DependentUpon>PageDownloadLeft.xaml</DependentUpon>
     </Compile>
-    <Compile Include="Pages\PageDownload\PageDownloadClient.xaml.vb">
-      <DependentUpon>PageDownloadClient.xaml</DependentUpon>
-    </Compile>
     <Compile Include="Pages\PageOther\PageOtherLeft.xaml.vb">
       <DependentUpon>PageOtherLeft.xaml</DependentUpon>
     </Compile>
@@ -556,10 +539,6 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
-    <Page Include="Pages\PageDownload\PageDownloadFabric.xaml">
-      <SubType>Designer</SubType>
-      <Generator>MSBuild:Compile</Generator>
-    </Page>
     <Page Include="Pages\PageDownload\PageDownloadInstall.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
@@ -568,10 +547,6 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
-    <Page Include="Pages\PageDownload\PageDownloadNeoForge.xaml">
-      <SubType>Designer</SubType>
-      <Generator>MSBuild:Compile</Generator>
-    </Page>
     <Page Include="Pages\PageLaunch\MyMsgLogin.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
@@ -625,18 +600,6 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
-    <Page Include="Pages\PageDownload\PageDownloadForge.xaml">
-      <SubType>Designer</SubType>
-      <Generator>MSBuild:Compile</Generator>
-    </Page>
-    <Page Include="Pages\PageDownload\PageDownloadLiteLoader.xaml">
-      <SubType>Designer</SubType>
-      <Generator>MSBuild:Compile</Generator>
-    </Page>
-    <Page Include="Pages\PageDownload\PageDownloadOptiFine.xaml">
-      <SubType>Designer</SubType>
-      <Generator>MSBuild:Compile</Generator>
-    </Page>
     <Page Include="Pages\PageOther\PageOtherTest.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
@@ -681,10 +644,6 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
-    <Page Include="Pages\PageDownload\PageDownloadClient.xaml">
-      <SubType>Designer</SubType>
-      <Generator>MSBuild:Compile</Generator>
-    </Page>
     <Page Include="Pages\PageOther\PageOtherLeft.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>

File diff suppressed because it is too large
+ 115 - 115
Plain Craft Launcher 2/Resources/ModData.txt


Some files were not shown because too many files changed in this diff