龙腾猫跃 před 3 roky
rodič
revize
3b8de89309
28 změnil soubory, kde provedl 613 přidání a 449 odebrání
  1. 2 2
      Plain Craft Launcher 2/Application.xaml
  2. 8 0
      Plain Craft Launcher 2/Application.xaml.vb
  3. 0 4
      Plain Craft Launcher 2/Controls/MyCard.vb
  4. 2 2
      Plain Craft Launcher 2/Controls/MyExtraButton.xaml
  5. 2 2
      Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb
  6. 27 21
      Plain Craft Launcher 2/Controls/MyPageLeft.vb
  7. 5 1
      Plain Craft Launcher 2/Controls/MyScrollViewer.vb
  8. 24 0
      Plain Craft Launcher 2/FormMain.xaml.vb
  9. 20 24
      Plain Craft Launcher 2/Modules/Base/ModBase.vb
  10. 9 14
      Plain Craft Launcher 2/Modules/Base/ModLoader.vb
  11. 11 29
      Plain Craft Launcher 2/Modules/Base/MyBitmap.vb
  12. 9 4
      Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
  13. 152 99
      Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
  14. 18 12
      Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
  15. 18 14
      Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
  16. 2 2
      Plain Craft Launcher 2/Modules/ModMusic.vb
  17. 2 2
      Plain Craft Launcher 2/My Project/AssemblyInfo.vb
  18. 108 74
      Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
  19. 2 2
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb
  20. 2 2
      Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
  21. 1 8
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb
  22. 1 1
      Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb
  23. 1 1
      Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb
  24. 1 0
      Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb
  25. 1 1
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
  26. 0 1
      Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb
  27. 185 127
      Plain Craft Launcher 2/Resources/Custom.xaml
  28. binární
      Plain Craft Launcher 2/Resources/Help.zip

+ 2 - 2
Plain Craft Launcher 2/Application.xaml

@@ -133,7 +133,7 @@
                 <Setter Property="Template">
                 <Setter Property="Template">
                     <Setter.Value>
                     <Setter.Value>
                         <ControlTemplate>
                         <ControlTemplate>
-                            <Border BorderBrush="{DynamicResource ColorBrush1}" BorderThickness="1.5" CornerRadius="4" MaxWidth="500" Background="#E5FFFFFF" Margin="0,0,4,4" SnapsToDevicePixels="True">
+                            <Border Loaded="TooltipLoaded" Unloaded="TooltipUnloaded" BorderBrush="{DynamicResource ColorBrush1}" BorderThickness="1.5" CornerRadius="4" MaxWidth="500" Background="#E5FFFFFF" Margin="0,0,4,4" SnapsToDevicePixels="True">
                                 <Border.Effect>
                                 <Border.Effect>
                                     <DropShadowEffect Opacity="0.3" BlurRadius="4" ShadowDepth="2" Color="{DynamicResource ColorObject1}" />
                                     <DropShadowEffect Opacity="0.3" BlurRadius="4" ShadowDepth="2" Color="{DynamicResource ColorObject1}" />
                                 </Border.Effect>
                                 </Border.Effect>
@@ -147,7 +147,7 @@
                 <Setter Property="Template">
                 <Setter Property="Template">
                     <Setter.Value>
                     <Setter.Value>
                         <ControlTemplate>
                         <ControlTemplate>
-                            <Border BorderBrush="{DynamicResource ColorBrush1}" BorderThickness="1" CornerRadius="3" Background="White" Margin="0,0,4,4" SnapsToDevicePixels="True">
+                            <Border Loaded="TooltipLoaded" Unloaded="TooltipUnloaded" BorderBrush="{DynamicResource ColorBrush1}" BorderThickness="1" CornerRadius="3" Background="White" Margin="0,0,4,4" SnapsToDevicePixels="True">
                                 <TextBlock Text="{TemplateBinding ContentControl.Content}" TextWrapping="Wrap" Foreground="{DynamicResource ColorBrush1}" Margin="7,5" FontSize="12"/>
                                 <TextBlock Text="{TemplateBinding ContentControl.Content}" TextWrapping="Wrap" Foreground="{DynamicResource ColorBrush1}" Margin="7,5" FontSize="12"/>
                             </Border>
                             </Border>
                         </ControlTemplate>
                         </ControlTemplate>

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

@@ -247,4 +247,12 @@ Public Class Application
         End Select
         End Select
     End Sub
     End Sub
 
 
+    Public Shared ShowingTooltips As New List(Of Border)
+    Private Sub TooltipLoaded(sender As Border, e As EventArgs)
+        ShowingTooltips.Add(sender)
+    End Sub
+    Private Sub TooltipUnloaded(sender As Border, e As RoutedEventArgs)
+        ShowingTooltips.Remove(sender)
+    End Sub
+
 End Class
 End Class

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

@@ -41,10 +41,6 @@
     End Sub
     End Sub
     Private IsLoad As Boolean = False
     Private IsLoad As Boolean = False
     Private Sub MyCard_Loaded() Handles Me.Loaded
     Private Sub MyCard_Loaded() Handles Me.Loaded
-        '卡片的进入时动画已被页面通用切换动画替代
-        'If LoadAnimation AndAlso IsLoad Then
-        '    StartHeightAnimation(Math.Min(50, ActualHeight), ActualHeight - Math.Min(50, ActualHeight), True)
-        'End If
         If IsLoad Then Exit Sub
         If IsLoad Then Exit Sub
         '初次加载限定
         '初次加载限定
         If Not Title = "" AndAlso IsNothing(MainTextBlock) Then
         If Not Title = "" AndAlso IsNothing(MainTextBlock) Then

+ 2 - 2
Plain Craft Launcher 2/Controls/MyExtraButton.xaml

@@ -16,9 +16,9 @@
                 <DropShadowEffect ShadowDepth="0" BlurRadius="10" Opacity="0.2" />
                 <DropShadowEffect ShadowDepth="0" BlurRadius="10" Opacity="0.2" />
             </Border.Effect>
             </Border.Effect>
         </Border>
         </Border>
-        <Border x:Name="PanProgress" CornerRadius="1000" BorderThickness="0.001" Background="#2AFFFFFF" ClipToBounds="True" Visibility="Collapsed">
+        <Border x:Name="PanProgress" CornerRadius="1000" BorderThickness="0.001" Background="#2FFFFFFF" ClipToBounds="True" Visibility="Collapsed">
             <Border.Clip>
             <Border.Clip>
-                <RectangleGeometry x:Name="RectProgress" Rect="0,0,40,25" />
+                <RectangleGeometry x:Name="RectProgress" Rect="0,5,40,35" />
             </Border.Clip>
             </Border.Clip>
         </Border>
         </Border>
         <Path x:Name="Path" Stretch="Uniform" Margin="12" Fill="{DynamicResource ColorBrush8}" RenderTransformOrigin="0.5,0.5">
         <Path x:Name="Path" Stretch="Uniform" Margin="12" Fill="{DynamicResource ColorBrush8}" RenderTransformOrigin="0.5,0.5">

+ 2 - 2
Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb

@@ -17,7 +17,7 @@
                 PanProgress.Visibility = Visibility.Collapsed
                 PanProgress.Visibility = Visibility.Collapsed
             Else
             Else
                 PanProgress.Visibility = Visibility.Visible
                 PanProgress.Visibility = Visibility.Visible
-                RectProgress.Rect = New Rect(0, 0, 40, 40 * (1 - value))
+                RectProgress.Rect = New Rect(0, 40 * (1 - value), 40, 40 * value)
             End If
             End If
         End Set
         End Set
     End Property
     End Property
@@ -179,7 +179,7 @@
 
 
                 AniStop("MyExtraButton Color " & Uuid)
                 AniStop("MyExtraButton Color " & Uuid)
                 If IsMouseOver Then
                 If IsMouseOver Then
-                    PanColor.SetResourceReference(BackgroundProperty, "ColorBrush2")
+                    PanColor.SetResourceReference(BackgroundProperty, "ColorBrush4")
                 Else
                 Else
                     PanColor.SetResourceReference(BackgroundProperty, "ColorBrush3")
                     PanColor.SetResourceReference(BackgroundProperty, "ColorBrush3")
                 End If
                 End If

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

@@ -29,21 +29,28 @@
             '逐个进入动画
             '逐个进入动画
             Dim AniList As New List(Of AniData)
             Dim AniList As New List(Of AniData)
             Dim Id As Integer = 0, Delay As Integer = 0
             Dim Id As Integer = 0, Delay As Integer = 0
-            Dim Controls = GetAllAnimControls()
-            For Each Element As FrameworkElement In Controls
-                Element.Opacity = 0
-                Element.RenderTransform = New TranslateTransform(-25, 0)
-                AniList.Add(AaOpacity(Element, If(TypeOf Element Is TextBlock, 0.6, 1), 200, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
-                AniList.Add(AaTranslateX(Element, 5, 200, Delay, New AniEaseOutFluent))
-                AniList.Add(AaTranslateX(Element, 20, 300, Delay, New AniEaseOutBack(AniEasePower.Weak)))
-                If TypeOf Element Is MyListItem Then
-                    AniList.Add(AaCode(Sub()
-                                           CType(Element, MyListItem).IsMouseOverAnimationEnabled = True
-                                           CType(Element, MyListItem).RefreshColor(Me, New EventArgs)
-                                       End Sub, Delay + 280))
+            For Each Element As FrameworkElement In GetAllAnimControls(True)
+                If Element.Visibility = Visibility.Collapsed Then
+                    '还原之前的隐藏动画可能导致的改变(#2436)
+                    Element.Opacity = 1
+                    Element.RenderTransform = New TranslateTransform(0, 0)
+                    If TypeOf Element Is MyListItem Then CType(Element, MyListItem).IsMouseOverAnimationEnabled = True
+                Else
+                    Element.Opacity = 0
+                    Element.RenderTransform = New TranslateTransform(-25, 0)
+                    If TypeOf Element Is MyListItem Then CType(Element, MyListItem).IsMouseOverAnimationEnabled = False
+                    AniList.Add(AaOpacity(Element, If(TypeOf Element Is TextBlock, 0.6, 1), 200, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
+                    AniList.Add(AaTranslateX(Element, 5, 200, Delay, New AniEaseOutFluent))
+                    AniList.Add(AaTranslateX(Element, 20, 300, Delay, New AniEaseOutBack(AniEasePower.Weak)))
+                    If TypeOf Element Is MyListItem Then
+                        AniList.Add(AaCode(Sub()
+                                               CType(Element, MyListItem).IsMouseOverAnimationEnabled = True
+                                               CType(Element, MyListItem).RefreshColor(Me, New EventArgs)
+                                           End Sub, Delay + 280))
+                    End If
+                    Delay += Math.Max(8, 20 - Id) * 2.5
+                    Id += 1
                 End If
                 End If
-                Delay += Math.Max(8, 20 - Id) * 2.5
-                Id += 1
             Next
             Next
             AniStart(AniList, "PageLeft PageChange " & Uuid)
             AniStart(AniList, "PageLeft PageChange " & Uuid)
         End If
         End If
@@ -74,23 +81,22 @@
     End Sub
     End Sub
 
 
     '遍历获取所有需要生成动画的控件
     '遍历获取所有需要生成动画的控件
-    Private Function GetAllAnimControls() As List(Of FrameworkElement)
+    Private Function GetAllAnimControls(Optional IgnoreInvisibility As Boolean = False) As List(Of FrameworkElement)
         Dim AllControls As New List(Of FrameworkElement)
         Dim AllControls As New List(Of FrameworkElement)
-        GetAllAnimControls(AnimatedControl, AllControls)
+        GetAllAnimControls(AnimatedControl, AllControls, IgnoreInvisibility)
         Return AllControls
         Return AllControls
     End Function
     End Function
-    Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement))
-        If Element.Visibility = Visibility.Collapsed Then Exit Sub
+    Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
+        If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Exit Sub
         If TypeOf Element Is MyTextButton Then
         If TypeOf Element Is MyTextButton Then
             AllControls.Add(Element)
             AllControls.Add(Element)
         ElseIf TypeOf Element Is MyListItem Then
         ElseIf TypeOf Element Is MyListItem Then
-            CType(Element, MyListItem).IsMouseOverAnimationEnabled = False
             AllControls.Add(Element)
             AllControls.Add(Element)
         ElseIf TypeOf Element Is ContentControl Then
         ElseIf TypeOf Element Is ContentControl Then
-            GetAllAnimControls(CType(Element, ContentControl).Content, AllControls)
+            GetAllAnimControls(CType(Element, ContentControl).Content, AllControls, IgnoreInvisibility)
         ElseIf TypeOf Element Is Panel Then
         ElseIf TypeOf Element Is Panel Then
             For Each Element2 As FrameworkElement In CType(Element, Panel).Children
             For Each Element2 As FrameworkElement In CType(Element, Panel).Children
-                GetAllAnimControls(Element2, AllControls)
+                GetAllAnimControls(Element2, AllControls, IgnoreInvisibility)
             Next
             Next
         Else
         Else
             AllControls.Add(Element)
             AllControls.Add(Element)

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

@@ -15,8 +15,12 @@
             '如果当前是在对有滚动条的下拉框或文本框执行,则不接管操作
             '如果当前是在对有滚动条的下拉框或文本框执行,则不接管操作
             Exit Sub
             Exit Sub
         End If
         End If
-        PerformVerticalOffsetDelta(-e.Delta)
         e.Handled = True
         e.Handled = True
+        PerformVerticalOffsetDelta(-e.Delta)
+        '关闭 Tooltip (#2552)
+        For Each TooltipBorder In Application.ShowingTooltips
+            AniStart(AaOpacity(TooltipBorder, -1, 100), $"Hide Tooltip {GetUuid()}")
+        Next
     End Sub
     End Sub
     Public Sub PerformVerticalOffsetDelta(Delta As Double)
     Public Sub PerformVerticalOffsetDelta(Delta As Double)
         AniStart(AaDouble(Sub(AnimDelta As Double)
         AniStart(AaDouble(Sub(AnimDelta As Double)

+ 24 - 0
Plain Craft Launcher 2/FormMain.xaml.vb

@@ -10,6 +10,11 @@ Public Class FormMain
         Dim FeatureList As New List(Of KeyValuePair(Of Integer, String))
         Dim FeatureList As New List(Of KeyValuePair(Of Integer, String))
         '统计更新日志条目
         '统计更新日志条目
 #If BETA Then
 #If BETA Then
+        If LastVersion < 304 Then 'Release 2.6.8
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复开始或结束游戏时可能报错的 Bug"))
+            FeatureCount += 20
+            BugCount += 25
+        End If
         If LastVersion < 302 Then 'Release 2.6.7
         If LastVersion < 302 Then 'Release 2.6.7
             FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "再次修复网络条件差时可能无法下载 MC 的 Bug"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "再次修复网络条件差时可能无法下载 MC 的 Bug"))
             BugCount += 1
             BugCount += 1
@@ -128,6 +133,11 @@ Public Class FormMain
         '3:BUG+ IMP* FEAT-
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '2:BUG* IMP-
         '1:BUG-
         '1:BUG-
+        If LastVersion < 305 Then 'Snapshot 2.6.8
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复开始或结束游戏时可能报错的 Bug"))
+            FeatureCount += 20
+            BugCount += 25
+        End If
         If LastVersion < 303 Then 'Snapshot 2.6.7
         If LastVersion < 303 Then 'Snapshot 2.6.7
             FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "再次修复网络条件差时可能无法下载 MC 的 Bug"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "再次修复网络条件差时可能无法下载 MC 的 Bug"))
             BugCount += 1
             BugCount += 1
@@ -331,6 +341,7 @@ Public Class FormMain
     End Sub
     End Sub
 
 
     '窗口加载
     '窗口加载
+    Private Declare Auto Function ChangeWindowMessageFilterEx Lib "user32.dll" (ByVal hwnd As IntPtr, ByVal message As UInt32, ByVal action As UInt32, ByVal changeInfo As UInt32) As Boolean
     Private IsWindowLoadFinished As Boolean = False
     Private IsWindowLoadFinished As Boolean = False
     ''' <summary>
     ''' <summary>
     ''' 是否为联机提权后自动重启。
     ''' 是否为联机提权后自动重启。
@@ -442,6 +453,7 @@ Public Class FormMain
         TimerMainStartRun()
         TimerMainStartRun()
         '加载池
         '加载池
         RunInNewThread(Sub()
         RunInNewThread(Sub()
+                           'EULA 提示
                            If Not Setup.Get("SystemEula") Then
                            If Not Setup.Get("SystemEula") Then
                                Select Case MyMsgBox("在使用 PCL 前,请同意 PCL 的用户协议与免责声明。", "协议授权", "同意", "拒绝", "查看用户协议与免责声明",
                                Select Case MyMsgBox("在使用 PCL 前,请同意 PCL 的用户协议与免责声明。", "协议授权", "同意", "拒绝", "查看用户协议与免责声明",
                                    Button3Action:=Sub() OpenWebsite("https://shimo.im/docs/rGrd8pY8xWkt6ryW"))
                                    Button3Action:=Sub() OpenWebsite("https://shimo.im/docs/rGrd8pY8xWkt6ryW"))
@@ -451,6 +463,7 @@ Public Class FormMain
                                        EndProgram(False)
                                        EndProgram(False)
                                End Select
                                End Select
                            End If
                            End If
+                           '启动加载器池
                            Try
                            Try
                                JavaListInit() '延后到同意协议后再执行,避免在初次启动时进行进程操作
                                JavaListInit() '延后到同意协议后再执行,避免在初次启动时进行进程操作
                                Thread.Sleep(200)
                                Thread.Sleep(200)
@@ -460,11 +473,22 @@ Public Class FormMain
                            Catch ex As Exception
                            Catch ex As Exception
                                Log(ex, "初始化加载池运行失败", LogLevel.Feedback)
                                Log(ex, "初始化加载池运行失败", LogLevel.Feedback)
                            End Try
                            End Try
+                           '清理自动更新文件
                            Try
                            Try
                                If File.Exists(Path & "PCL\Plain Craft Launcher 2.exe") Then File.Delete(Path & "PCL\Plain Craft Launcher 2.exe")
                                If File.Exists(Path & "PCL\Plain Craft Launcher 2.exe") Then File.Delete(Path & "PCL\Plain Craft Launcher 2.exe")
                            Catch ex As Exception
                            Catch ex As Exception
                                Log(ex, "清理自动更新文件失败")
                                Log(ex, "清理自动更新文件失败")
                            End Try
                            End Try
+                           '开启管理员权限下的文件拖拽(#2531)
+                           Try
+                               If IsAdmin() Then
+                                   Log("[System] PCL 正以管理员权限运行")
+                                   ChangeWindowMessageFilterEx(Handle, &H233, 1, Nothing)
+                                   ChangeWindowMessageFilterEx(Handle, &H49, 1, Nothing)
+                               End If
+                           Catch ex As Exception
+                               Log(ex, "开启管理员权限下的文件拖拽失败")
+                           End Try
                        End Sub, "Start Loader", ThreadPriority.Lowest)
                        End Sub, "Start Loader", ThreadPriority.Lowest)
 
 
         Log("[Start] 第三阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
         Log("[Start] 第三阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")

+ 20 - 24
Plain Craft Launcher 2/Modules/Base/ModBase.vb

@@ -11,12 +11,12 @@ Public Module ModBase
 #Region "声明"
 #Region "声明"
 
 
     '下列版本信息由更新器自动修改
     '下列版本信息由更新器自动修改
-    Public Const VersionBaseName As String = "2.6.7" '不含分支前缀的显示用版本名
-    Public Const VersionStandardCode As String = "2.6.7." & VersionBranchCode '标准格式的四段式版本号
+    Public Const VersionBaseName As String = "2.6.8" '不含分支前缀的显示用版本名
+    Public Const VersionStandardCode As String = "2.6.8." & VersionBranchCode '标准格式的四段式版本号
 #If BETA Then
 #If BETA Then
-    Public Const VersionCode As Integer = 302 'Release
+    Public Const VersionCode As Integer = 304 'Release
 #Else
 #Else
-    Public Const VersionCode As Integer = 303 'Snapshot
+    Public Const VersionCode As Integer = 305 'Snapshot
 #End If
 #End If
     '自动生成的版本信息
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -1242,23 +1242,7 @@ Re:
     Public Function ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing) As Boolean
     Public Function ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing) As Boolean
         Try
         Try
             Directory.CreateDirectory(DestDirectory)
             Directory.CreateDirectory(DestDirectory)
-            If CompressFilePath.EndsWith(".zip") OrElse CompressFilePath.EndsWith(".jar") OrElse CompressFilePath.EndsWith(".mrpack") Then
-                '以 zip 方式解压
-                Using stream As New FileStream(CompressFilePath, FileMode.Open)
-                    Using archive As New ZipArchive(stream, ZipArchiveMode.Read, False, If(Encode, Encoding.GetEncoding("GB18030")))
-                        For Each entry As ZipArchiveEntry In archive.Entries
-                            Dim destinationPath As String = IO.Path.Combine(DestDirectory, entry.FullName)
-                            If destinationPath.EndsWith("\") OrElse destinationPath.EndsWith("/") Then
-                                Directory.CreateDirectory(destinationPath)
-                            Else
-                                If File.Exists(destinationPath) Then File.Delete(destinationPath)
-                                entry.ExtractToFile(destinationPath, True)
-                            End If
-                        Next
-                    End Using
-                End Using
-                Return True
-            ElseIf CompressFilePath.EndsWith(".gz") Then
+            If CompressFilePath.EndsWith(".gz") Then
                 '以 gz 方式解压
                 '以 gz 方式解压
                 Dim stream As New GZipStream(New FileStream(CompressFilePath, FileMode.Open, FileAccess.ReadWrite), CompressionMode.Decompress)
                 Dim stream As New GZipStream(New FileStream(CompressFilePath, FileMode.Open, FileAccess.ReadWrite), CompressionMode.Decompress)
                 Dim decompressedFile As New FileStream(DestDirectory & GetFileNameFromPath(CompressFilePath).ToLower.Replace(".tar", "").Replace(".gz", ""), FileMode.OpenOrCreate, FileAccess.Write)
                 Dim decompressedFile As New FileStream(DestDirectory & GetFileNameFromPath(CompressFilePath).ToLower.Replace(".tar", "").Replace(".gz", ""), FileMode.OpenOrCreate, FileAccess.Write)
@@ -1271,7 +1255,19 @@ Re:
                 stream.Close()
                 stream.Close()
                 Return True
                 Return True
             Else
             Else
-                Return False
+                '以 zip 方式解压
+                Using Archive = ZipFile.Open(CompressFilePath, ZipArchiveMode.Read, If(Encode, Encoding.GetEncoding("GB18030")))
+                    For Each Entry As ZipArchiveEntry In Archive.Entries
+                        Dim DestinationPath As String = IO.Path.Combine(DestDirectory, Entry.FullName)
+                        If DestinationPath.EndsWith("\") OrElse DestinationPath.EndsWith("/") Then
+                            Continue For '不创建空文件夹
+                        Else
+                            Directory.CreateDirectory(GetPathFromFullPath(DestinationPath))
+                            Entry.ExtractToFile(DestinationPath, True)
+                        End If
+                    Next
+                End Using
+                Return True
             End If
             End If
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "尝试解压文件失败")
             Log(ex, "尝试解压文件失败")
@@ -1301,7 +1297,7 @@ Re:
         Next
         Next
         Temp = Directory.GetDirectories(Path)
         Temp = Directory.GetDirectories(Path)
         For Each str As String In Temp
         For Each str As String In Temp
-            DeleteDirectory(str)
+            DeleteDirectory(str, IgnoreIssue)
         Next
         Next
         Try
         Try
             Directory.Delete(Path, True)
             Directory.Delete(Path, True)
@@ -1578,7 +1574,7 @@ Re:
     ''' 为字符串进行 XML 转义。
     ''' 为字符串进行 XML 转义。
     ''' </summary>
     ''' </summary>
     Public Function EscapeXML(Str As String) As String
     Public Function EscapeXML(Str As String) As String
-        Return Str.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("'", "&apos;").Replace("""", "&quot;")
+        Return Str.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("'", "&apos;").Replace("""", "&quot;").Replace(vbCrLf, "&#xa;")
     End Function
     End Function
 
 
     '正则
     '正则

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

@@ -437,10 +437,6 @@
                             For Each Loader In Loaders
                             For Each Loader In Loaders
                                 Loader.Abort()
                                 Loader.Abort()
                             Next
                             Next
-                            '从列表删除
-                            SyncLock LoaderTaskbarLock
-                                If LoaderTaskbar.Contains(Me) Then LoaderTaskbar.Remove(Me)
-                            End SyncLock
                         End Sub)
                         End Sub)
         End Sub
         End Sub
 
 
@@ -571,6 +567,7 @@ Restart:
         If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRemove(Loader)
         If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRemove(Loader)
         SyncLock LoaderTaskbarLock
         SyncLock LoaderTaskbarLock
             LoaderTaskbar.Add(Loader)
             LoaderTaskbar.Add(Loader)
+            Log($"[Taskbar] {Loader.Name} 已加入任务列表")
         End SyncLock
         End SyncLock
     End Sub
     End Sub
     Public Sub LoaderTaskbarProgressRefresh()
     Public Sub LoaderTaskbarProgressRefresh()
@@ -585,16 +582,13 @@ Restart:
                     If Loader.Show AndAlso Loader.State = LoadState.Loading Then IsAllDownloadTaskCompleted = False
                     If Loader.Show AndAlso Loader.State = LoadState.Loading Then IsAllDownloadTaskCompleted = False
                 Next
                 Next
                 '若单个任务已中止或全部任务已完成,则刷新并移除
                 '若单个任务已中止或全部任务已完成,则刷新并移除
-                Dim RemovedTask As New List(Of LoaderBase)
-                For i = 0 To LoaderTaskbar.Count - 1
-                    If (IsAllDownloadTaskCompleted OrElse LoaderTaskbar(i).State = LoadState.Aborted) AndAlso LoaderTaskbar(i).Show Then
-                        If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRefresh(LoaderTaskbar(i))
-                        RemovedTask.Add(LoaderTaskbar(i))
+                For Each Task In LoaderTaskbar.ToList()
+                    If (IsAllDownloadTaskCompleted OrElse Task.State = LoadState.Aborted OrElse Task.State = LoadState.Waiting) AndAlso Task.Show Then
+                        If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRefresh(Task)
+                        LoaderTaskbar.Remove(Task)
+                        Log($"[Taskbar] {Task.Name} 已移出任务列表")
                     End If
                     End If
                 Next
                 Next
-                For Each Task In RemovedTask
-                    LoaderTaskbar.Remove(Task)
-                Next
             End SyncLock
             End SyncLock
             '更新平滑后的进度
             '更新平滑后的进度
             If NewProgress <= 0 OrElse NewProgress >= 1 OrElse LoaderTaskbarProgress > NewProgress Then
             If NewProgress <= 0 OrElse NewProgress >= 1 OrElse LoaderTaskbarProgress > NewProgress Then
@@ -625,8 +619,9 @@ Restart:
         Try
         Try
             Dim Total As Double = 0, Count As Integer = 0
             Dim Total As Double = 0, Count As Integer = 0
             SyncLock LoaderTaskbarLock
             SyncLock LoaderTaskbarLock
-                For i = 0 To LoaderTaskbar.Count - 1
-                    Total += LoaderTaskbar(i).Progress
+                For Each Task In LoaderTaskbar.ToList
+                    If Not Task.Show Then Continue For
+                    Total += Task.Progress
                     Count += 1 '避免多线程影响导致计数出错
                     Count += 1 '避免多线程影响导致计数出错
                 Next
                 Next
             End SyncLock
             End SyncLock

+ 11 - 29
Plain Craft Launcher 2/Modules/Base/MyBitmap.vb

@@ -28,7 +28,7 @@ Public Class MyBitmap
     Public Shared Widening Operator CType(Image As MyBitmap) As ImageSource
     Public Shared Widening Operator CType(Image As MyBitmap) As ImageSource
         Dim Bitmap = Image.Pic
         Dim Bitmap = Image.Pic
         Dim rect = New System.Drawing.Rectangle(0, 0, Bitmap.Width, Bitmap.Height)
         Dim rect = New System.Drawing.Rectangle(0, 0, Bitmap.Width, Bitmap.Height)
-        Dim bitmapData = Bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb)
+        Dim bitmapData = Bitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
         Try
         Try
             Dim size = rect.Width * rect.Height * 4
             Dim size = rect.Width * rect.Height * 4
             Return BitmapSource.Create(Bitmap.Width, Bitmap.Height, Bitmap.HorizontalResolution, Bitmap.VerticalResolution, PixelFormats.Bgra32, Nothing, bitmapData.Scan0, size, bitmapData.Stride)
             Return BitmapSource.Create(Bitmap.Width, Bitmap.Height, Bitmap.HorizontalResolution, Bitmap.VerticalResolution, PixelFormats.Bgra32, Nothing, bitmapData.Scan0, size, bitmapData.Stride)
@@ -104,45 +104,27 @@ Public Class MyBitmap
         End Using
         End Using
     End Sub
     End Sub
 
 
-    ''' <summary>
-    ''' 获取旋转的图片,这个方法不会导致原对象改变且会返回一个新的对象。图片长宽必须相等。
-    ''' </summary>
-    ''' <param name="angle">旋转角度(单位为角度)。</param>
-    Public Function Rotation(angle As Double) As System.Drawing.Bitmap
-        Dim img As System.Drawing.Image = Me.Pic
-        Dim bitSize As Single = img.Width
-        Dim bmp As New System.Drawing.Bitmap(CInt(bitSize), CInt(bitSize), img.PixelFormat)
-        bmp.SetResolution(img.HorizontalResolution, img.VerticalResolution)
-        Using g As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(bmp)
-            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor
-            g.TranslateTransform(bitSize / 2, bitSize / 2)
-            g.RotateTransform(angle)
-            g.TranslateTransform(-bitSize / 2, -bitSize / 2)
-            g.DrawImage(img, New System.Drawing.Rectangle(0, 0, img.Width, img.Width))
-        End Using
-        Return bmp
-    End Function
-
     ''' <summary>
     ''' <summary>
     ''' 获取裁切的图片,这个方法不会导致原对象改变且会返回一个新的对象。
     ''' 获取裁切的图片,这个方法不会导致原对象改变且会返回一个新的对象。
     ''' </summary>
     ''' </summary>
-    Public Function Clip(rect As System.Drawing.Rectangle) As System.Drawing.Bitmap
-        Dim img As System.Drawing.Image = Pic
-        Dim bmp As New System.Drawing.Bitmap(rect.Width, rect.Height, img.PixelFormat)
-        bmp.SetResolution(img.HorizontalResolution, img.VerticalResolution)
+    Public Function Clip(X As Integer, Y As Integer, Width As Integer, Height As Integer) As MyBitmap
+        Dim bmp As New System.Drawing.Bitmap(Width, Height, Pic.PixelFormat)
+        bmp.SetResolution(Pic.HorizontalResolution, Pic.VerticalResolution)
         Using g As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(bmp)
         Using g As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(bmp)
             g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor
             g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor
-            g.DrawImageUnscaled(img, rect.X, rect.Y)
+            g.TranslateTransform(-X, -Y)
+            g.DrawImage(Pic, New System.Drawing.Rectangle(0, 0, Pic.Width, Pic.Height))
         End Using
         End Using
         Return bmp
         Return bmp
     End Function
     End Function
 
 
     ''' <summary>
     ''' <summary>
-    ''' 获取左右翻转的图片,这个方法不会导致原对象改变。
+    ''' 获取旋转或翻转后的图片,这个方法不会导致原对象改变且会返回一个新的对象。
     ''' </summary>
     ''' </summary>
-    Public Function LeftRightFilp() As System.Drawing.Bitmap
+    Public Function RotateFlip(Type As System.Drawing.RotateFlipType) As MyBitmap
         Dim bmp As New System.Drawing.Bitmap(Pic)
         Dim bmp As New System.Drawing.Bitmap(Pic)
-        bmp.RotateFlip(System.Drawing.RotateFlipType.RotateNoneFlipX)
+        bmp.SetResolution(Pic.HorizontalResolution, Pic.VerticalResolution)
+        bmp.RotateFlip(Type)
         Return bmp
         Return bmp
     End Function
     End Function
 
 
@@ -152,7 +134,7 @@ Public Class MyBitmap
     Public Sub Save(FilePath As String)
     Public Sub Save(FilePath As String)
         Dim encoder As BitmapEncoder = New PngBitmapEncoder()
         Dim encoder As BitmapEncoder = New PngBitmapEncoder()
         encoder.Frames.Add(BitmapFrame.Create(Me))
         encoder.Frames.Add(BitmapFrame.Create(Me))
-        Using fileStream = New System.IO.FileStream(FilePath, FileMode.Create)
+        Using fileStream As New FileStream(FilePath, FileMode.Create)
             encoder.Save(fileStream)
             encoder.Save(fileStream)
         End Using
         End Using
     End Sub
     End Sub

+ 9 - 4
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb

@@ -434,7 +434,7 @@
                 Dim NormalNameList = New List(Of String)
                 Dim NormalNameList = New List(Of String)
                 For Each Name In NameLists
                 For Each Name In NameLists
                     Dim LowerName As String = Name.ToLower
                     Dim LowerName As String = Name.ToLower
-                    If Name.ToUpper = Name Then
+                    If Name.ToUpper = Name AndAlso Name <> "FPS" AndAlso Name <> "HUD" Then
                         '缩写
                         '缩写
                         ExNameList.Add(Name)
                         ExNameList.Add(Name)
                     ElseIf (LowerName.Contains("forge") OrElse LowerName.Contains("fabric") OrElse LowerName.Contains("quilt")) AndAlso
                     ElseIf (LowerName.Contains("forge") OrElse LowerName.Contains("fabric") OrElse LowerName.Contains("quilt")) AndAlso
@@ -487,8 +487,10 @@ NoExName:
                     '将 “Forge” 等提示改为 “Forge 版”
                     '将 “Forge” 等提示改为 “Forge 版”
                     If (Ex.ToLower.Contains("forge") OrElse Ex.ToLower.Contains("fabric") OrElse Ex.ToLower.Contains("quilt")) AndAlso Not Ex.Contains("版") AndAlso
                     If (Ex.ToLower.Contains("forge") OrElse Ex.ToLower.Contains("fabric") OrElse Ex.ToLower.Contains("quilt")) AndAlso Not Ex.Contains("版") AndAlso
                         Ex.ToLower.Replace("forge", "").Replace("fabric", "").Replace("quilt", "").Length <= 3 Then
                         Ex.ToLower.Replace("forge", "").Replace("fabric", "").Replace("quilt", "").Length <= 3 Then
-                        Ex = Ex.Replace("Edition", "").Trim.Capitalize & " 版"
+                        Ex = Ex.Replace("Edition", "").Replace("edition", "").Trim.Capitalize & " 版"
                     End If
                     End If
+                    '将 “forge” 等词语的首字母大写
+                    Ex = Ex.Replace("forge", "Forge").Replace("neo", "Neo").Replace("fabric", "Fabric").Replace("quilt", "Quilt")
                     ExName &= "  |  " & Ex.Trim
                     ExName &= "  |  " & Ex.Trim
                 Next
                 Next
                 If ExName = "" Then GoTo NoExName
                 If ExName = "" Then GoTo NoExName
@@ -933,7 +935,10 @@ Retry:
                             Next
                             Next
                             '更新结果
                             '更新结果
                             SyncLock ResultsLock
                             SyncLock ResultsLock
-                                RawResults.AddRange(ProjectList)
+                                For Each Project In ProjectList
+                                    If Task.Input.Type = CompType.Mod AndAlso Not Project.ModLoaders.Any() Then Continue For '过滤插件(#2458)
+                                    RawResults.Add(Project)
+                                Next
                             End SyncLock
                             End SyncLock
                             Storage.ModrinthOffset += ProjectList.Count
                             Storage.ModrinthOffset += ProjectList.Count
                             Storage.ModrinthTotal = RequestResult("total_hits").ToObject(Of Integer)
                             Storage.ModrinthTotal = RequestResult("total_hits").ToObject(Of Integer)
@@ -988,7 +993,7 @@ Retry:
                                                   Not Storage.Results.Any(Function(b) r.IsLike(b))).ToList
                                                   Not Storage.Results.Any(Function(b) r.IsLike(b))).ToList
         '加入列表
         '加入列表
         RealResults.AddRange(RawResults)
         RealResults.AddRange(RawResults)
-        Log($"[Comp] 去重后累计新增结果 {RealResults.Count} 个")
+        Log($"[Comp] 去重、筛选后累计新增结果 {RealResults.Count} 个")
 
 
 #End Region
 #End Region
 
 

+ 152 - 99
Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb

@@ -127,7 +127,7 @@
         End Try
         End Try
 
 
         '导入其中的日志文件
         '导入其中的日志文件
-        For Each TargetFile As FileInfo In New DirectoryInfo(TempFolder & "Temp\").EnumerateFiles
+        For Each TargetFile As FileInfo In New DirectoryInfo(TempFolder & "Temp\").EnumerateFiles.ToList()
             Try
             Try
                 If Not TargetFile.Exists OrElse TargetFile.Length = 0 Then Continue For
                 If Not TargetFile.Exists OrElse TargetFile.Length = 0 Then Continue For
                 Dim Ext As String = TargetFile.Extension.ToLower
                 Dim Ext As String = TargetFile.Extension.ToLower
@@ -137,6 +137,8 @@
                     Else
                     Else
                         AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(TargetFile.FullName, ReadFile(TargetFile.FullName, Encoding.UTF8).Split(vbCrLf.ToCharArray)))
                         AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(TargetFile.FullName, ReadFile(TargetFile.FullName, Encoding.UTF8).Split(vbCrLf.ToCharArray)))
                     End If
                     End If
+                Else
+                    File.Delete(TargetFile.FullName)
                 End If
                 End If
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "导入单个日志文件失败")
                 Log(ex, "导入单个日志文件失败")
@@ -235,6 +237,7 @@
                         End If
                         End If
                     Case AnalyzeFileType.MinecraftLog
                     Case AnalyzeFileType.MinecraftLog
                         LogMc = ""
                         LogMc = ""
+                        LogMcDebug = ""
                         '创建文件名词典
                         '创建文件名词典
                         Dim FileNameDict As New Dictionary(Of String, KeyValuePair(Of String, String()))
                         Dim FileNameDict As New Dictionary(Of String, KeyValuePair(Of String, String()))
                         For Each SelectedFile In SelectedFiles
                         For Each SelectedFile In SelectedFiles
@@ -272,11 +275,21 @@
                                 Exit For
                                 Exit For
                             End If
                             End If
                         Next
                         Next
+                        '查找 Debug Log
+                        For Each FileName As String In {"debug.log", "debug log.txt"}
+                            If FileNameDict.ContainsKey(FileName) Then
+                                Dim CurrentLog = FileNameDict(FileName)
+                                LogMcDebug += GetHeadTailLines(CurrentLog.Value, 1000, 0)
+                                Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为 Minecraft Debug 日志")
+                                Exit For
+                            End If
+                        Next
                         '检查错误
                         '检查错误
                         If LogMc = "" Then
                         If LogMc = "" Then
                             LogMc = Nothing
                             LogMc = Nothing
                             Throw New Exception("无法找到匹配的 Minecraft Log")
                             Throw New Exception("无法找到匹配的 Minecraft Log")
                         End If
                         End If
+                        If LogMcDebug = "" Then LogMcDebug = Nothing
                     Case AnalyzeFileType.ExtraLog
                     Case AnalyzeFileType.ExtraLog
                         '全部丢过去
                         '全部丢过去
                         For Each SelectedFile In SelectedFiles
                         For Each SelectedFile In SelectedFiles
@@ -294,7 +307,7 @@
         If ResultCount = 0 Then
         If ResultCount = 0 Then
             Log("[Crash] 步骤 2:准备日志文本完成,没有任何可供分析的日志")
             Log("[Crash] 步骤 2:准备日志文本完成,没有任何可供分析的日志")
         Else
         Else
-            Log(("[Crash] 步骤 2:准备日志文本完成,找到" & If(LogMc Is Nothing, "", "游戏日志、") & If(LogHs Is Nothing, "", "虚拟机日志、") & If(LogCrash Is Nothing, "", "崩溃日志、")).TrimEnd("、") & "用作分析")
+            Log(("[Crash] 步骤 2:准备日志文本完成,找到" & If(LogMc Is Nothing, "", "游戏日志、") & If(LogMcDebug Is Nothing, "", "游戏 Debug 日志、") & If(LogHs Is Nothing, "", "虚拟机日志、") & If(LogCrash Is Nothing, "", "崩溃日志、")).TrimEnd("、") & "用作分析")
         End If
         End If
         Return ResultCount
         Return ResultCount
 
 
@@ -315,7 +328,7 @@
     End Function
     End Function
 
 
     '3:根据文本分析崩溃原因
     '3:根据文本分析崩溃原因
-    Private LogMc As String = Nothing, LogHs As String = Nothing, LogCrash As String = Nothing
+    Private LogMc As String = Nothing, LogMcDebug As String = Nothing, LogHs As String = Nothing, LogCrash As String = Nothing
     Private LogAll As String
     Private LogAll As String
     '可能导致崩溃的原因与附加信息
     '可能导致崩溃的原因与附加信息
     Private CrashReasons As New Dictionary(Of CrashReason, List(Of String))
     Private CrashReasons As New Dictionary(Of CrashReason, List(Of String))
@@ -324,6 +337,7 @@
     ''' </summary>
     ''' </summary>
     Private Enum CrashReason
     Private Enum CrashReason
         Mod文件被解压
         Mod文件被解压
+        MixinBootstrap缺失
         内存不足
         内存不足
         使用JDK
         使用JDK
         显卡不支持OpenGL
         显卡不支持OpenGL
@@ -344,9 +358,8 @@
         ModMixin失败
         ModMixin失败
         Mod加载器报错
         Mod加载器报错
         Mod初始化失败
         Mod初始化失败
-        崩溃日志堆栈分析发现关键字
-        崩溃日志堆栈分析发现Mod名称
-        MC日志堆栈分析发现关键字
+        堆栈分析发现关键字
+        堆栈分析发现Mod名称
         OptiFine导致无法加载世界 'https://www.minecraftforum.net/forums/support/java-edition-support/3051132-exception-ticking-world
         OptiFine导致无法加载世界 'https://www.minecraftforum.net/forums/support/java-edition-support/3051132-exception-ticking-world
         特定方块导致崩溃
         特定方块导致崩溃
         特定实体导致崩溃
         特定实体导致崩溃
@@ -372,7 +385,6 @@
     ''' </summary>
     ''' </summary>
     Public Sub Analyze(Optional Version As McVersion = Nothing)
     Public Sub Analyze(Optional Version As McVersion = Nothing)
         Log("[Crash] 步骤 3:分析崩溃原因")
         Log("[Crash] 步骤 3:分析崩溃原因")
-        LogAll = (If(LogMc, "") & If(LogHs, "") & If(LogCrash, "")).ToLower
 
 
         '1. 精准日志匹配,中/高优先级
         '1. 精准日志匹配,中/高优先级
         AnalyzeCrit1()
         AnalyzeCrit1()
@@ -381,37 +393,41 @@
         If CrashReasons.Count > 0 Then GoTo Done
         If CrashReasons.Count > 0 Then GoTo Done
 
 
         '2. 堆栈分析
         '2. 堆栈分析
-        If LogAll.Contains("forge") OrElse LogAll.Contains("fabric") OrElse LogAll.Contains("liteloader") Then
-            '来源崩溃日志的分析
+        LogAll = (If(LogMc, "") & If(LogMcDebug, "") & If(LogHs, "") & If(LogCrash, ""))
+        If LogAll.Contains("orge") OrElse LogAll.Contains("abric") OrElse LogAll.Contains("uilt") OrElse LogAll.Contains("iteloader") Then
+            Dim Keywords As New List(Of String)
+            '崩溃日志
             If LogCrash IsNot Nothing Then
             If LogCrash IsNot Nothing Then
                 Log("[Crash] 开始进行崩溃日志堆栈分析")
                 Log("[Crash] 开始进行崩溃日志堆栈分析")
                 Dim StackLogs As String = LogCrash.Split("System Details").First
                 Dim StackLogs As String = LogCrash.Split("System Details").First
-                Dim Keywords = AnalyzeStackKeyword(StackLogs)
-                If Keywords.Count > 0 Then
-                    Dim Names = AnalyzeModName(Keywords)
-                    If Names Is Nothing Then
-                        AppendReason(CrashReason.崩溃日志堆栈分析发现关键字, Keywords)
-                    Else
-                        AppendReason(CrashReason.崩溃日志堆栈分析发现Mod名称, Names)
-                    End If
-                    GoTo Done
-                End If
+                Keywords.AddRange(AnalyzeStackKeyword(StackLogs))
             End If
             End If
-            '来自 Minecraft 日志的分析
+            'Minecraft 日志
             If LogMc IsNot Nothing Then
             If LogMc IsNot Nothing Then
                 Dim Fatals = RegexSearch(LogMc, "/FATAL] [\w\W]+?(?=[\n]+\[)")
                 Dim Fatals = RegexSearch(LogMc, "/FATAL] [\w\W]+?(?=[\n]+\[)")
                 Log("[Crash] 开始进行 Minecraft 日志堆栈分析,发现 " & Fatals.Count & " 个报错项")
                 Log("[Crash] 开始进行 Minecraft 日志堆栈分析,发现 " & Fatals.Count & " 个报错项")
                 If Fatals.Count > 0 Then
                 If Fatals.Count > 0 Then
-                    Dim Keywords As New List(Of String)
                     For Each Fatal In Fatals
                     For Each Fatal In Fatals
                         Keywords.AddRange(AnalyzeStackKeyword(Fatal))
                         Keywords.AddRange(AnalyzeStackKeyword(Fatal))
                     Next
                     Next
-                    If Keywords.Count > 0 Then
-                        AppendReason(CrashReason.MC日志堆栈分析发现关键字, Keywords.Distinct.ToList)
-                        GoTo Done
-                    End If
                 End If
                 End If
             End If
             End If
+            '虚拟机日志
+            If LogHs IsNot Nothing Then
+                Log("[Crash] 开始进行虚拟机堆栈分析")
+                Dim StackLogs As String = LogHs.Split("Registers:").First.Split("T H R E A D").Last
+                Keywords.AddRange(AnalyzeStackKeyword(StackLogs))
+            End If
+            'Mod 名称分析
+            If Keywords.Count > 0 Then
+                Dim Names = AnalyzeModName(Keywords)
+                If Names Is Nothing Then
+                    AppendReason(CrashReason.堆栈分析发现关键字, Keywords)
+                Else
+                    AppendReason(CrashReason.堆栈分析发现Mod名称, Names)
+                End If
+                GoTo Done
+            End If
         Else
         Else
             Log("[Crash] 可能并未安装 Mod,不进行堆栈分析")
             Log("[Crash] 可能并未安装 Mod,不进行堆栈分析")
         End If
         End If
@@ -563,6 +579,19 @@ Done:
     ''' </summary>
     ''' </summary>
     Private Sub AnalyzeCrit2()
     Private Sub AnalyzeCrit2()
 
 
+        '游戏日志分析
+        If LogMc IsNot Nothing Then
+            '常规信息
+            If LogMc.Contains("An exception was thrown, the game will display an error screen and halt.") Then AppendReason(CrashReason.Forge报错, RegexSeek(LogMc, "(?<=the game will display an error screen and halt.[\n\r]+[^\n]+?Exception: )[\s\S]+?(?=\n\tat)")?.Trim(vbCrLf))
+            If LogMc.Contains("A potential solution has been determined:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf))
+        End If
+
+    End Sub
+    ''' <summary>
+    ''' 进行精准日志匹配。匹配优先级低于堆栈分析的崩溃。
+    ''' </summary>
+    Private Sub AnalyzeCrit3()
+
         '游戏日志分析
         '游戏日志分析
         If LogMc IsNot Nothing Then
         If LogMc IsNot Nothing Then
             'Mixin 崩溃
             'Mixin 崩溃
@@ -572,28 +601,17 @@ Done:
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=in mixins.)[^./ ]+(?=.json.+failed injection check)")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=in mixins.)[^./ ]+(?=.json.+failed injection check)")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in )[^./ ]+(?=.mixins.json)")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in )[^./ ]+(?=.mixins.json)")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in mixins.)[^./ ]+(?=.json)")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in mixins.)[^./ ]+(?=.json)")
+                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed mixins.)[^./ ]+(?=.json:)")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[)[^./ ]+(?=.mixins.json\] FAILED during )")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[)[^./ ]+(?=.mixins.json\] FAILED during )")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[mixins.)[^./ ]+(?=.json\] FAILED during )")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[mixins.)[^./ ]+(?=.json\] FAILED during )")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=from mod )[^./ ]+(?=\] from)")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=from mod )[^./ ]+(?=\] from)")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=for mod )[^./ ]+(?= failed)")
                 If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=for mod )[^./ ]+(?= failed)")
                 '兜底名称判断
                 '兜底名称判断
-                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "[^./ \]]+(?=.mixins.json")
-                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=mixins.)[^./ \]]+(?=.json")
+                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "[^./ \]]+(?=.mixins.json)")
+                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=mixins.)[^./ \]]+(?=.json)")
                 AppendReason(CrashReason.ModMixin失败, TryAnalyzeModName(If(ModId, "").TrimEnd((vbCrLf & " ").ToCharArray)))
                 AppendReason(CrashReason.ModMixin失败, TryAnalyzeModName(If(ModId, "").TrimEnd((vbCrLf & " ").ToCharArray)))
             End If
             End If
-            '常规信息
-            If LogMc.Contains("An exception was thrown, the game will display an error screen and halt.") Then AppendReason(CrashReason.Forge报错, RegexSeek(LogMc, "(?<=the game will display an error screen and halt.[\n\r]+[^\n]+?Exception: )[\s\S]+?(?=\n\tat)")?.Trim(vbCrLf))
-            If LogMc.Contains("A potential solution has been determined:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf))
-        End If
-
-    End Sub
-    ''' <summary>
-    ''' 进行精准日志匹配。匹配优先级低于堆栈分析的崩溃。
-    ''' </summary>
-    Private Sub AnalyzeCrit3()
-
-        '游戏日志分析
-        If LogMc IsNot Nothing Then
+            '极短的程序输出
             If Not (LogMc.Contains("at net.") OrElse LogMc.Contains("INFO]")) AndAlso LogHs Is Nothing AndAlso LogCrash Is Nothing AndAlso LogMc.Length < 100 Then
             If Not (LogMc.Contains("at net.") OrElse LogMc.Contains("INFO]")) AndAlso LogHs Is Nothing AndAlso LogCrash Is Nothing AndAlso LogMc.Length < 100 Then
                 AppendReason(CrashReason.极短的程序输出, LogMc)
                 AppendReason(CrashReason.极短的程序输出, LogMc)
             End If
             End If
@@ -667,7 +685,7 @@ NextStack:
 
 
     End Function
     End Function
     ''' <summary>
     ''' <summary>
-    ''' 根据 Mod 关键词与详细信息(崩溃报告第二部分)与,获取实际的 Mod 名称。
+    ''' 根据 Mod 关键词尝试获取实际的 Mod 名称。
     ''' 若失败则返回 Nothing。
     ''' 若失败则返回 Nothing。
     ''' </summary>
     ''' </summary>
     Private Function AnalyzeModName(Keywords As List(Of String)) As List(Of String)
     Private Function AnalyzeModName(Keywords As List(Of String)) As List(Of String)
@@ -682,60 +700,95 @@ NextStack:
         Next
         Next
         Keywords = RealKeywords
         Keywords = RealKeywords
 
 
-        '获取崩溃报告对应部分
-        If LogCrash Is Nothing Then Return Nothing
-        If Not LogCrash.Contains("A detailed walkthrough of the error") Then Return Nothing
-        Dim Details As String = LogCrash.Replace("A detailed walkthrough of the error", "¨")
-        Dim IsFabricDetail As Boolean = Details.Contains("Fabric Mods") '是否为 Fabric 信息格式
-        If IsFabricDetail Then
-            Details = Details.Replace("Fabric Mods", "¨")
-            Log("[Crash] 检测到 Fabric Mod 信息格式")
+        '从崩溃报告获取 Mod 信息
+        If LogCrash IsNot Nothing AndAlso LogCrash.Contains("A detailed walkthrough of the error") Then
+            Dim Details As String = LogCrash.Replace("A detailed walkthrough of the error", "¨")
+            Dim IsFabricDetail As Boolean = Details.Contains("Fabric Mods") '是否为 Fabric 信息格式
+            If IsFabricDetail Then
+                Details = Details.Replace("Fabric Mods", "¨")
+                Log("[Crash] 崩溃报告中检测到 Fabric Mod 信息格式")
+            End If
+            Details = Details.Split("¨").Last
+
+            '[Forge] 获取所有包含 .jar 的行
+            '[Fabric] 获取所有包含 Mod 信息的行
+            Dim ModNameLines As New List(Of String)
+            For Each Line In Details.Split(vbLf)
+                If Line.ToLower.Contains(".jar") OrElse
+                   (IsFabricDetail AndAlso Line.StartsWith(vbTab & vbTab) AndAlso Not RegexCheck(Line, "\t\tfabric[\w-]*: Fabric")) Then ModNameLines.Add(Line)
+            Next
+            Log("[Crash] 崩溃报告中找到 " & ModNameLines.Count & " 个可能的 Mod 项目行")
+
+            '获取 Mod ID 与关键词的匹配行
+            Dim HintLines As New List(Of String)
+            For Each KeyWord As String In Keywords
+                For Each ModString As String In ModNameLines
+                    Dim RealModString As String = ModString.ToLower.Replace("_", "")
+                    If Not RealModString.Contains(KeyWord.ToLower.Replace("_", "")) Then Continue For
+                    If RealModString.Contains("minecraft.jar") OrElse RealModString.Contains(" forge-") OrElse RealModString.Contains(" mixin-") Then Continue For
+                    HintLines.Add(ModString.Trim(vbCrLf.ToCharArray))
+                    Exit For
+                Next
+            Next
+            HintLines = HintLines.Distinct.ToList
+            Log("[Crash] 崩溃报告中找到 " & HintLines.Count & " 个可能的崩溃 Mod 匹配行")
+            For Each ModLine As String In HintLines
+                Log("[Crash]  - " & ModLine)
+            Next
+
+            '从 Mod 匹配行中提取 .jar 文件的名称
+            For Each Line As String In HintLines
+                Dim Name As String
+                If IsFabricDetail Then
+                    Name = RegexSeek(Line, "(?<=: )[^\n]+(?= [^\n]+)")
+                Else
+                    Name = RegexSeek(Line, "(?<=\()[^\t]+.jar(?=\))|(?<=(\t\t)|(\| ))[^\t\|]+.jar", RegularExpressions.RegexOptions.IgnoreCase)
+                End If
+                If Name IsNot Nothing Then ModFileNames.Add(Name)
+            Next
+
         End If
         End If
-        Details = Details.Split("¨").Last
-
-        '[Forge] 获取所有包含 .jar 的行
-        '[Fabric] 获取所有包含 Mod 信息的行
-        Dim ModNameLines As New List(Of String)
-        For Each Line In Details.Split(vbLf)
-            If Line.ToLower.Contains(".jar") OrElse
-               (IsFabricDetail AndAlso Line.StartsWith(vbTab & vbTab) AndAlso Not RegexCheck(Line, "\t\tfabric[\w-]*: Fabric")) Then ModNameLines.Add(Line)
-        Next
-        Log("[Crash] 找到 " & ModNameLines.Count & " 个可能的 Mod 项目行")
-
-        '获取 Mod ID 与关键词的匹配行
-        Dim HintLines As New List(Of String)
-        For Each KeyWord As String In Keywords
-            For Each ModString As String In ModNameLines
-                Dim RealModString As String = ModString.ToLower.Replace("_", "")
-                If Not RealModString.Contains(KeyWord.ToLower.Replace("_", "")) Then Continue For
-                If RealModString.Contains("minecraft.jar") OrElse RealModString.Contains(" forge-") OrElse RealModString.Contains(" mixin-") Then Continue For
-                HintLines.Add(ModString.Trim(vbCrLf.ToCharArray))
-                Exit For
+
+        '从 debug.log 获取 Mod 信息
+        If LogMcDebug IsNot Nothing Then
+
+            'Forge: Found valid mod file YungsBetterStrongholds-1.20-Forge-4.0.1.jar with {betterstrongholds} mods - versions {1.20-Forge-4.0.1}
+            Dim ModNameLines As List(Of String) = RegexSearch(LogMcDebug, "(?<=valid mod file ).*", RegularExpressions.RegexOptions.Multiline)
+            Log("[Crash] Debug 信息中找到 " & ModNameLines.Count & " 个可能的 Mod 项目行")
+
+            '获取 Mod ID 与关键词的匹配行
+            Dim HintLines As New List(Of String)
+            For Each KeyWord As String In Keywords
+                For Each ModString As String In ModNameLines
+                    If ModString.Contains($"{{{KeyWord}}}") Then HintLines.Add(ModString)
+                Next
+            Next
+            HintLines = HintLines.Distinct.ToList
+            Log("[Crash] Debug 信息中找到 " & HintLines.Count & " 个可能的崩溃 Mod 匹配行")
+            For Each ModLine As String In HintLines
+                Log("[Crash]  - " & ModLine)
             Next
             Next
-        Next
-        HintLines = HintLines.Distinct.ToList
-        Log("[Crash] 找到 " & HintLines.Count & " 个可能的崩溃 Mod 匹配行")
-        For Each ModName As String In HintLines
-            Log("[Crash]  - " & ModName)
-        Next
 
 
-        '从 Mod 匹配行中提取 .jar 文件的名称
-        For Each Line As String In HintLines
-            Dim Name As String
-            If IsFabricDetail Then
-                Name = RegexSeek(Line, "(?<=: )[^\n]+(?= [^\n]+)")
-            Else
-                Name = RegexSeek(Line, "(?<=\()[^\t]+.jar(?=\))|(?<=(\t\t)|(\| ))[^\t\|]+.jar", RegularExpressions.RegexOptions.IgnoreCase)
-            End If
-            If Name IsNot Nothing Then ModFileNames.Add(Name)
-        Next
-        ModFileNames = ModFileNames.Distinct.ToList
-        Log("[Crash] 找到 " & ModFileNames.Count & " 个可能的崩溃 Mod 文件名")
-        For Each ModFileName As String In ModFileNames
-            Log("[Crash]  - " & ModFileName)
-        Next
+            '从 Mod 匹配行中提取 .jar 文件的名称
+            For Each Line As String In HintLines
+                Dim Name As String
+                Name = RegexSeek(Line, ".*(?= with)")
+                If Name IsNot Nothing Then ModFileNames.Add(Name)
+            Next
 
 
-        Return If(ModFileNames.Count = 0, Nothing, ModFileNames)
+        End If
+
+        '输出
+        ModFileNames = ModFileNames.Distinct.ToList
+        If ModFileNames.Count = 0 Then
+            Return Nothing
+        Else
+            Log("[Crash] 找到 " & ModFileNames.Count & " 个可能的崩溃 Mod 文件名")
+            For Each ModFileName As String In ModFileNames
+                Log("[Crash]  - " & ModFileName)
+            Next
+            Return ModFileNames
+        End If
     End Function
     End Function
     ''' <summary>
     ''' <summary>
     ''' 尝试获取 Mod 名称,若失败则返回原关键字。
     ''' 尝试获取 Mod 名称,若失败则返回原关键字。
@@ -841,26 +894,26 @@ NextStack:
                 Case CrashReason.Mod名称包含特殊字符
                 Case CrashReason.Mod名称包含特殊字符
                     Results.Add("由于有 Mod 的名称包含特殊字符,导致游戏崩溃。\n请尝试修改 Mod 文件名,让它只包含英文字母、数字、减号(-)、下划线(_)和小数点,然后再启动游戏。")
                     Results.Add("由于有 Mod 的名称包含特殊字符,导致游戏崩溃。\n请尝试修改 Mod 文件名,让它只包含英文字母、数字、减号(-)、下划线(_)和小数点,然后再启动游戏。")
                 Case CrashReason.MixinBootstrap缺失
                 Case CrashReason.MixinBootstrap缺失
-                    Results.Add("由于缺失 MixinBootstrap,导致游戏崩溃。\n请尝试安装 MixinBootstrap 解决。若安装后崩溃,尝试在该文件名前添加英文“!”后再试")
+                    Results.Add("由于缺失 MixinBootstrap,导致游戏崩溃。\n请尝试安装 MixinBootstrap。若安装后依然崩溃,可以尝试在文件名前添加英文感叹号。")
                 Case CrashReason.使用32位Java导致JVM无法分配足够多的内存
                 Case CrashReason.使用32位Java导致JVM无法分配足够多的内存
                     If Environment.Is64BitOperatingSystem Then
                     If Environment.Is64BitOperatingSystem Then
                         Results.Add("你似乎正在使用 32 位 Java,这会导致 Minecraft 无法使用所需的内存,进而造成崩溃。\n\n请在启动设置的 Java 选择一项中改用 64 位的 Java 再启动游戏,然后再启动游戏。\n如果你没有安装 64 位的 Java,你可以从网络中下载、安装一个。")
                         Results.Add("你似乎正在使用 32 位 Java,这会导致 Minecraft 无法使用所需的内存,进而造成崩溃。\n\n请在启动设置的 Java 选择一项中改用 64 位的 Java 再启动游戏,然后再启动游戏。\n如果你没有安装 64 位的 Java,你可以从网络中下载、安装一个。")
                     Else
                     Else
                         Results.Add("你正在使用 32 位的操作系统,这会导致 Minecraft 无法使用所需的内存,进而造成崩溃。\n\n你或许只能重装 64 位的操作系统来解决此问题。\n如果你的电脑内存在 2GB 以内,那或许只能换台电脑了……\h")
                         Results.Add("你正在使用 32 位的操作系统,这会导致 Minecraft 无法使用所需的内存,进而造成崩溃。\n\n你或许只能重装 64 位的操作系统来解决此问题。\n如果你的电脑内存在 2GB 以内,那或许只能换台电脑了……\h")
                     End If
                     End If
-                Case CrashReason.崩溃日志堆栈分析发现关键字, CrashReason.MC日志堆栈分析发现关键字
-                    If Additional.Count = 1 Then
-                        Results.Add("你的游戏遇到了一些问题,这可能是某些 Mod 所引起的,PCL 找到了一个可疑的关键词:" & Additional.First & "。\n\n如果你知道它对应的 Mod,那么有可能就是它引起的错误,你也可以查看错误报告获取详情。\h")
-                    Else
-                        Results.Add("你的游戏遇到了一些问题,这可能是某些 Mod 所引起的,PCL 找到了以下可疑的关键词:\n - " & Join(Additional, ", ") & "\n\n如果你知道这些关键词对应的 Mod,那么有可能就是它引起的错误,你也可以查看错误报告获取详情。\h")
-                    End If
                 Case CrashReason.Mod缺少前置或MC版本错误
                 Case CrashReason.Mod缺少前置或MC版本错误
                     If Additional.Count > 0 Then
                     If Additional.Count > 0 Then
                         Results.Add("由于未满足 Mod 的依赖项,导致游戏退出。\n未满足的依赖项:\n - " & Join(Additional, "\n - ") & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
                         Results.Add("由于未满足 Mod 的依赖项,导致游戏退出。\n未满足的依赖项:\n - " & Join(Additional, "\n - ") & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
                     Else
                     Else
                         Results.Add("由于未满足 Mod 的依赖项,导致游戏退出。\n请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h")
                         Results.Add("由于未满足 Mod 的依赖项,导致游戏退出。\n请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h")
                     End If
                     End If
-                Case CrashReason.崩溃日志堆栈分析发现Mod名称
+                Case CrashReason.堆栈分析发现关键字
+                    If Additional.Count = 1 Then
+                        Results.Add("你的游戏遇到了一些问题,这可能是某些 Mod 所引起的,PCL 找到了一个可疑的关键词:" & Additional.First & "。\n\n如果你知道它对应的 Mod,那么有可能就是它引起的错误,你也可以查看错误报告获取详情。\h")
+                    Else
+                        Results.Add("你的游戏遇到了一些问题,这可能是某些 Mod 所引起的,PCL 找到了以下可疑的关键词:\n - " & Join(Additional, ", ") & "\n\n如果你知道这些关键词对应的 Mod,那么有可能就是它引起的错误,你也可以查看错误报告获取详情。\h")
+                    End If
+                Case CrashReason.堆栈分析发现Mod名称
                     If Additional.Count = 1 Then
                     If Additional.Count = 1 Then
                         Results.Add("名为 " & Additional.First & " 的 Mod 可能导致了游戏出错。\n\e\h")
                         Results.Add("名为 " & Additional.First & " 的 Mod 可能导致了游戏出错。\n\e\h")
                     Else
                     Else

+ 18 - 12
Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb

@@ -1141,14 +1141,11 @@ SystemBrowser:
                 '1.15:Java 8 - 15
                 '1.15:Java 8 - 15
                 MinVer = New Version(1, 8, 0, 0) : MaxVer = New Version(1, 15, 999, 999)
                 MinVer = New Version(1, 8, 0, 0) : MaxVer = New Version(1, 15, 999, 999)
             ElseIf VersionSortBoolean(McVersionCurrent.Version.ForgeVersion, "34.0.0") AndAlso VersionSortBoolean("36.2.25", McVersionCurrent.Version.ForgeVersion) Then
             ElseIf VersionSortBoolean(McVersionCurrent.Version.ForgeVersion, "34.0.0") AndAlso VersionSortBoolean("36.2.25", McVersionCurrent.Version.ForgeVersion) Then
-                '1.16.X,Forge 34.X ~ 36.2.25:最高 Java 8u320
+                '1.16,Forge 34.X ~ 36.2.25:最高 Java 8u320
                 MaxVer = New Version(1, 8, 0, 320)
                 MaxVer = New Version(1, 8, 0, 320)
-            ElseIf McVersionCurrent.Version.McCodeMain >= 18 AndAlso McVersionCurrent.Version.McCodeMain < 99 AndAlso McVersionCurrent.Version.HasOptiFine Then '#305
-                '1.18+:若安装了 OptiFine,最高 Java 18
+            ElseIf McVersionCurrent.Version.McCodeMain >= 18 AndAlso McVersionCurrent.Version.McCodeMain < 19 AndAlso McVersionCurrent.Version.HasOptiFine Then '#305
+                '1.18:若安装了 OptiFine,最高 Java 18
                 MaxVer = New Version(1, 18, 999, 999)
                 MaxVer = New Version(1, 18, 999, 999)
-            ElseIf McVersionCurrent.Version.McCodeMain >= 19 AndAlso McVersionCurrent.Version.McCodeMain < 99 Then
-                '1.19+:最高 Java 19
-                MaxVer = New Version(1, 19, 999, 999)
             End If
             End If
         End If
         End If
 
 
@@ -1753,6 +1750,15 @@ NextVersion:
 
 
         '更新 options.txt
         '更新 options.txt
         Dim SetupFileAddress As String = McVersionCurrent.PathIndie & "options.txt"
         Dim SetupFileAddress As String = McVersionCurrent.PathIndie & "options.txt"
+        If Not File.Exists(SetupFileAddress) Then
+            'Yosbr Mod 兼容(#2385):https://www.curseforge.com/minecraft/mc-mods/yosbr
+            Dim YosbrFileAddress As String = McVersionCurrent.PathIndie & "config\yosbr\options.txt"
+            If File.Exists(YosbrFileAddress) Then
+                McLaunchLog("将修改 Yosbr Mod 中的 options.txt")
+                SetupFileAddress = YosbrFileAddress
+                WriteIni(SetupFileAddress, "lang", "none") '忽略默认语言
+            End If
+        End If
         Try
         Try
             '语言
             '语言
             '1.0-     :没有语言选项
             '1.0-     :没有语言选项
@@ -1773,11 +1779,11 @@ NextVersion:
                 WriteIni(SetupFileAddress, "lang", RequiredLang)
                 WriteIni(SetupFileAddress, "lang", RequiredLang)
                 McLaunchLog($"已将语言从 {CurrentLang} 修改为 {RequiredLang}")
                 McLaunchLog($"已将语言从 {CurrentLang} 修改为 {RequiredLang}")
             End If
             End If
-            '如果是初次设置,一并修改 forceUnicodeFont
-            If Setup.Get("ToolHelpChinese") AndAlso (CurrentLang = "none" OrElse Not Directory.Exists(McVersionCurrent.PathIndie & "saves")) Then
-                WriteIni(SetupFileAddress, "forceUnicodeFont", "true")
-                McLaunchLog("已开启 forceUnicodeFont")
-            End If
+            ''如果是初次设置,一并修改 forceUnicodeFont
+            'If Setup.Get("ToolHelpChinese") AndAlso (CurrentLang = "none" OrElse Not Directory.Exists(McVersionCurrent.PathIndie & "saves")) Then
+            '    WriteIni(SetupFileAddress, "forceUnicodeFont", "true")
+            '    McLaunchLog("已开启 forceUnicodeFont")
+            'End If
             '窗口
             '窗口
             Select Case Setup.Get("LaunchArgumentWindowType")
             Select Case Setup.Get("LaunchArgumentWindowType")
                 Case 0 '全屏
                 Case 0 '全屏
@@ -1861,7 +1867,7 @@ NextVersion:
                 Dim Skin As New MyBitmap(PathAppdata & "CustomSkin.png")
                 Dim Skin As New MyBitmap(PathAppdata & "CustomSkin.png")
                 If (McVersionCurrent.Version.McCodeMain = 6 OrElse McVersionCurrent.Version.McCodeMain = 7) AndAlso Skin.Pic.Height = 64 Then
                 If (McVersionCurrent.Version.McCodeMain = 6 OrElse McVersionCurrent.Version.McCodeMain = 7) AndAlso Skin.Pic.Height = 64 Then
                     McLaunchLog("该 Minecraft 版本不支持双层皮肤,已进行裁剪")
                     McLaunchLog("该 Minecraft 版本不支持双层皮肤,已进行裁剪")
-                    Skin = Skin.Clip(New System.Drawing.Rectangle(0, 0, 64, 32))
+                    Skin = Skin.Clip(0, 0, 64, 32)
                 End If
                 End If
                 Skin.Save(Path & "PCL\CustomSkin_Cliped.png")
                 Skin.Save(Path & "PCL\CustomSkin_Cliped.png")
                 '构建压缩文件
                 '构建压缩文件

+ 18 - 14
Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb

@@ -1,4 +1,6 @@
-Public Module ModModpack
+Imports System.IO.Compression
+
+Public Module ModModpack
 
 
     '触发整合包安装的外部接口
     '触发整合包安装的外部接口
     ''' <summary>
     ''' <summary>
@@ -109,7 +111,7 @@
     '整合包缓存清理
     '整合包缓存清理
     Private IsInstallCacheCleared As Boolean = False
     Private IsInstallCacheCleared As Boolean = False
     Private IsInstallCacheClearing As Boolean = False
     Private IsInstallCacheClearing As Boolean = False
-    Private Sub UnpackFiles(InstallTemp As String, FileAddress As String)
+    Private Sub UnpackFiles(InstallTemp As String, FileAddress As String, Loader As LoaderBase)
         '清理缓存文件夹
         '清理缓存文件夹
         If Not IsInstallCacheCleared Then
         If Not IsInstallCacheCleared Then
             IsInstallCacheCleared = True
             IsInstallCacheCleared = True
@@ -135,9 +137,8 @@
         Try
         Try
 Retry:
 Retry:
             '完全不知道为啥会出现文件正在被另一进程使用的问题,总之多试试
             '完全不知道为啥会出现文件正在被另一进程使用的问题,总之多试试
-            Directory.CreateDirectory(InstallTemp)
             DeleteDirectory(InstallTemp)
             DeleteDirectory(InstallTemp)
-            Compression.ZipFile.ExtractToDirectory(FileAddress, InstallTemp, Encode)
+            ExtractFile(FileAddress, InstallTemp, Encode)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "第 " & RetryCount & " 次解压尝试失败")
             Log(ex, "第 " & RetryCount & " 次解压尝试失败")
             If TypeOf ex Is ArgumentException Then
             If TypeOf ex Is ArgumentException Then
@@ -146,6 +147,7 @@ Retry:
             End If
             End If
             If RetryCount < 5 Then
             If RetryCount < 5 Then
                 Thread.Sleep(RetryCount * 2000)
                 Thread.Sleep(RetryCount * 2000)
+                If Loader IsNot Nothing AndAlso Loader.LoadingState <> MyLoading.MyLoadingState.Run Then Exit Sub
                 RetryCount += 1
                 RetryCount += 1
                 GoTo Retry
                 GoTo Retry
             Else
             Else
@@ -215,13 +217,15 @@ Retry:
         If OverrideHome <> "" Then
         If OverrideHome <> "" Then
             InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
             InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
             Sub(Task As LoaderTask(Of String, Integer))
             Sub(Task As LoaderTask(Of String, Integer))
-                UnpackFiles(InstallTemp, FileAddress)
+                UnpackFiles(InstallTemp, FileAddress, Task)
                 Task.Progress = 0.5
                 Task.Progress = 0.5
+                Dim OverridePath As String = InstallTemp & ArchiveBaseFolder & OverrideHome
                 '复制结果
                 '复制结果
-                If Directory.Exists(InstallTemp & ArchiveBaseFolder & OverrideHome) Then
-                    CopyDirectory(InstallTemp & ArchiveBaseFolder & OverrideHome, PathMcFolder & "versions\" & VersionName)
+                If Directory.Exists(OverridePath) Then
+                    CopyDirectory(OverridePath, PathMcFolder & "versions\" & VersionName)
+                    Log($"[ModPack] 整合包 override 复制:{OverridePath} -> {PathMcFolder & "versions\" & VersionName}")
                 Else
                 Else
-                    Log("[ModPack] 整合包中未找到 override 目录,已跳过")
+                    Log($"[ModPack] 整合包中未找到 override 文件夹:{OverridePath}")
                 End If
                 End If
                 Task.Progress = 0.9
                 Task.Progress = 0.9
                 '开启版本隔离
                 '开启版本隔离
@@ -393,7 +397,7 @@ Retry:
         Dim InstallLoaders As New List(Of LoaderBase)
         Dim InstallLoaders As New List(Of LoaderBase)
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
             Sub(Task As LoaderTask(Of String, Integer))
             Sub(Task As LoaderTask(Of String, Integer))
-                UnpackFiles(InstallTemp, FileAddress)
+                UnpackFiles(InstallTemp, FileAddress, Task)
                 Task.Progress = 0.5
                 Task.Progress = 0.5
                 '复制 overrides 文件夹和 client-overrides 文件夹
                 '复制 overrides 文件夹和 client-overrides 文件夹
                 If Directory.Exists(InstallTemp & ArchiveBaseFolder & "overrides") Then
                 If Directory.Exists(InstallTemp & ArchiveBaseFolder & "overrides") Then
@@ -425,7 +429,7 @@ Retry:
                 End Select
                 End Select
             End If
             End If
             '添加下载文件
             '添加下载文件
-            FileList.Add(New NetFile(File("downloads").Select(Function(t) t.ToString).ToArray,
+            FileList.Add(New NetFile(File("downloads").Select(Function(t) t.ToString.Replace("://edge.forgecdn", "://media.forgecdn")).ToArray, '修复 #2390
                                      PathMcFolder & "versions\" & VersionName & "\" & File("path").ToString,
                                      PathMcFolder & "versions\" & VersionName & "\" & File("path").ToString,
                                      New FileChecker(ActualSize:=File("fileSize").ToObject(Of Long), Hash:=File("hashes")("sha1").ToString)))
                                      New FileChecker(ActualSize:=File("fileSize").ToObject(Of Long), Hash:=File("hashes")("sha1").ToString)))
         Next
         Next
@@ -520,7 +524,7 @@ Retry:
         Dim InstallLoaders As New List(Of LoaderBase)
         Dim InstallLoaders As New List(Of LoaderBase)
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         Sub(Task As LoaderTask(Of String, Integer))
         Sub(Task As LoaderTask(Of String, Integer))
-            UnpackFiles(InstallTemp, FileAddress)
+            UnpackFiles(InstallTemp, FileAddress, Task)
             Task.Progress = 0.5
             Task.Progress = 0.5
             '复制结果
             '复制结果
             If Directory.Exists(InstallTemp & ArchiveBaseFolder & "minecraft") Then
             If Directory.Exists(InstallTemp & ArchiveBaseFolder & "minecraft") Then
@@ -619,7 +623,7 @@ Retry:
         Dim InstallLoaders As New List(Of LoaderBase)
         Dim InstallLoaders As New List(Of LoaderBase)
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         Sub(Task As LoaderTask(Of String, Integer))
         Sub(Task As LoaderTask(Of String, Integer))
-            UnpackFiles(InstallTemp, FileAddress)
+            UnpackFiles(InstallTemp, FileAddress, Task)
             Task.Progress = 0.5
             Task.Progress = 0.5
             '复制结果
             '复制结果
             If Directory.Exists(InstallTemp & ArchiveBaseFolder & ".minecraft") Then
             If Directory.Exists(InstallTemp & ArchiveBaseFolder & ".minecraft") Then
@@ -714,7 +718,7 @@ Retry:
         Dim InstallLoaders As New List(Of LoaderBase)
         Dim InstallLoaders As New List(Of LoaderBase)
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         Sub(Task As LoaderTask(Of String, Integer))
         Sub(Task As LoaderTask(Of String, Integer))
-            UnpackFiles(InstallTemp, FileAddress)
+            UnpackFiles(InstallTemp, FileAddress, Task)
             Task.Progress = 0.5
             Task.Progress = 0.5
             '复制结果
             '复制结果
             If Directory.Exists(InstallTemp & ArchiveBaseFolder & "overrides") Then
             If Directory.Exists(InstallTemp & ArchiveBaseFolder & "overrides") Then
@@ -805,7 +809,7 @@ Retry:
         If String.IsNullOrWhiteSpace(NewName) Then Exit Sub
         If String.IsNullOrWhiteSpace(NewName) Then Exit Sub
         '解压
         '解压
         Hint("正在解压压缩包……")
         Hint("正在解压压缩包……")
-        UnpackFiles(TargetFolder, FileAddress)
+        UnpackFiles(TargetFolder, FileAddress, Nothing)
         '加入文件夹列表
         '加入文件夹列表
         PageSelectLeft.AddFolder(TargetFolder, NewName, False)
         PageSelectLeft.AddFolder(TargetFolder, NewName, False)
         Hint("已加入游戏文件夹列表:" & NewName, HintType.Finish)
         Hint("已加入游戏文件夹列表:" & NewName, HintType.Finish)

+ 2 - 2
Plain Craft Launcher 2/Modules/ModMusic.vb

@@ -25,7 +25,7 @@
                 For Each File In My.Computer.FileSystem.GetFiles(Path & "PCL\Musics\", FileIO.SearchOption.SearchAllSubDirectories, "*.*")
                 For Each File In My.Computer.FileSystem.GetFiles(Path & "PCL\Musics\", FileIO.SearchOption.SearchAllSubDirectories, "*.*")
                     '文件夹可能会被加入 .ini 文件夹配置文件、一些乱七八糟的 .jpg 文件啥的
                     '文件夹可能会被加入 .ini 文件夹配置文件、一些乱七八糟的 .jpg 文件啥的
                     Dim Extend As String = File.Split(".").Last.ToLower
                     Dim Extend As String = File.Split(".").Last.ToLower
-                    If Not (Extend = "ini" OrElse Extend = "jpg" OrElse Extend = "txt" OrElse Extend = "cfg" OrElse Extend = "lrc" OrElse Extend = "png") Then
+                    If Not (Extend = "ini" OrElse Extend = "jpg" OrElse Extend = "txt" OrElse Extend = "cfg" OrElse Extend = "lrc" OrElse Extend = "db" OrElse Extend = "png") Then
                         MusicAllList.Add(File)
                         MusicAllList.Add(File)
                     End If
                     End If
                 Next
                 Next
@@ -212,7 +212,7 @@
     ''' 继续音乐播放,返回是否成功切换了状态。
     ''' 继续音乐播放,返回是否成功切换了状态。
     ''' </summary>
     ''' </summary>
     Public Function MusicResume() As Boolean
     Public Function MusicResume() As Boolean
-        If MusicState = MusicStates.Play Then
+        If MusicState = MusicStates.Play OrElse Not MusicAllList.Any() Then
             Return False
             Return False
         Else
         Else
             RunInThread(Sub()
             RunInThread(Sub()

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

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

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

@@ -196,7 +196,7 @@ Public Module ModDownloadLib
         Else
         Else
             NewItem.Info = Entry("lore").ToString
             NewItem.Info = Entry("lore").ToString
         End If
         End If
-        If Entry("url").ToString.Contains("pcl") Then NewItem.Info = "[PCL 特别提供] " & NewItem.Info
+        If Entry("url").ToString.Contains("pcl") Then NewItem.Info = "[PCL 特供下载] " & NewItem.Info
         AddHandler NewItem.Click, OnClick
         AddHandler NewItem.Click, OnClick
         '建立菜单
         '建立菜单
         If IsSaveOnly Then
         If IsSaveOnly Then
@@ -299,8 +299,8 @@ Public Module ModDownloadLib
             WikiName = "Java版Infdev_20100618"
             WikiName = "Java版Infdev_20100618"
         ElseIf Id = "c0.30_01c" OrElse Id = "c0.30_survival" OrElse Id.Contains("生存测试") Then
         ElseIf Id = "c0.30_01c" OrElse Id = "c0.30_survival" OrElse Id.Contains("生存测试") Then
             WikiName = "Java版Classic_0.30(生存模式)"
             WikiName = "Java版Classic_0.30(生存模式)"
-        ElseIf Id = "c0.31" Then
-            WikiName = "Java版Indev_0.31(2010年1月30日)"
+        ElseIf Id.StartsWith("c0.31") Then
+            WikiName = "Java版Indev_0.31_20100130"
         ElseIf Id.StartsWith("c") Then
         ElseIf Id.StartsWith("c") Then
             WikiName = "Java版" & Id.Replace("c", "Classic_")
             WikiName = "Java版" & Id.Replace("c", "Classic_")
         ElseIf Id.StartsWith("rd-") Then
         ElseIf Id.StartsWith("rd-") Then
@@ -382,7 +382,7 @@ Public Module ModDownloadLib
             Log(ex, "开始 OptiFine 下载失败", LogLevel.Feedback)
             Log(ex, "开始 OptiFine 下载失败", LogLevel.Feedback)
         End Try
         End Try
     End Sub
     End Sub
-    Private Sub McDownloadOptiFineInstall(BaseMcFolderHome As String, Target As String, Task As LoaderTask(Of List(Of NetFile), Boolean))
+    Private Sub McDownloadOptiFineInstall(BaseMcFolderHome As String, Target As String, Task As LoaderTask(Of List(Of NetFile), Boolean), UseJavaWrapper As Boolean)
         '选择 Java
         '选择 Java
         Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
         Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
         If Java Is Nothing Then
         If Java Is Nothing Then
@@ -399,7 +399,12 @@ Public Module ModDownloadLib
             If Java Is Nothing Then Throw New Exception("由于未找到 Java,已取消安装。")
             If Java Is Nothing Then Throw New Exception("由于未找到 Java,已取消安装。")
         End If
         End If
         '添加 Java Wrapper 作为主 Jar
         '添加 Java Wrapper 作为主 Jar
-        Dim Arguments = "-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """ -Duser.home=""" & BaseMcFolderHome & """ -cp """ & Target & """ -jar """ & ExtractJavaWrapper() & """ optifine.Installer"
+        Dim Arguments As String
+        If UseJavaWrapper Then
+            Arguments = $"-Doolloo.jlw.tmpdir=""{GetJavaWrapperDir()}"" -Duser.home=""{BaseMcFolderHome}"" -cp ""{Target}"" -jar ""{ExtractJavaWrapper()}"" optifine.Installer"
+        Else
+            Arguments = $"-Duser.home=""{BaseMcFolderHome}"" -cp ""{Target}"" optifine.Installer"
+        End If
         If Java.VersionCode >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments
         If Java.VersionCode >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments
         '开始启动
         '开始启动
         SyncLock InstallSyncLock
         SyncLock InstallSyncLock
@@ -504,67 +509,66 @@ Public Module ModDownloadLib
 
 
         '获取下载地址
         '获取下载地址
         Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("获取 OptiFine 主文件下载地址",
         Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("获取 OptiFine 主文件下载地址",
-                                                            Sub(Task As LoaderTask(Of String, List(Of NetFile)))
-                                                                '启动依赖版本的下载
-                                                                If ClientDownloadLoader Is Nothing Then
-                                                                    If IsCustomFolder Then Throw New Exception("如果没有指定原版下载器,则不能指定 MC 安装文件夹")
-                                                                    ClientDownloadLoader = McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, DownloadInfo.Inherit)
-                                                                End If
-                                                                Task.Progress = 0.1
-                                                                Dim Sources As New List(Of String)
-                                                                'BMCLAPI 源
-                                                                If DownloadInfo.IsPreview Then
-                                                                    Sources.Add("https://download.mcbbs.net/optifine/" & DownloadInfo.Inherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
-                                                                    Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
-                                                                Else
-                                                                    Sources.Add("https://download.mcbbs.net/optifine/" & DownloadInfo.Inherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
-                                                                    Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
-                                                                End If
-                                                                '官方源
-                                                                Dim PageData As String
-                                                                Try
-                                                                    PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html")
-                                                                    Task.Progress = 0.8
-                                                                    Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
-                                                                    Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources(0))
-                                                                Catch ex As Exception
-                                                                    Log(ex, "获取 OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址失败")
-                                                                End Try
-                                                                Task.Progress = 0.9
-                                                                'OptiFine 中文镜像源
-                                                                Sources.Add("https://optifine.cn/download/" & DownloadInfo.NameFile)
-                                                                '构造文件请求
-                                                                Task.Output = New List(Of NetFile) From {New NetFile(Sources.ToArray, Target, New FileChecker(MinSize:=64 * 1024))}
-                                                            End Sub) With {.ProgressWeight = 8})
+            Sub(Task As LoaderTask(Of String, List(Of NetFile)))
+                '启动依赖版本的下载
+                If ClientDownloadLoader Is Nothing Then
+                    If IsCustomFolder Then Throw New Exception("如果没有指定原版下载器,则不能指定 MC 安装文件夹")
+                    ClientDownloadLoader = McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, DownloadInfo.Inherit)
+                End If
+                Task.Progress = 0.1
+                Dim Sources As New List(Of String)
+                'BMCLAPI 源
+                If DownloadInfo.IsPreview Then
+                    Sources.Add("https://download.mcbbs.net/optifine/" & DownloadInfo.Inherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
+                    Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
+                Else
+                    Sources.Add("https://download.mcbbs.net/optifine/" & DownloadInfo.Inherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
+                    Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
+                End If
+                '官方源
+                Dim PageData As String
+                Try
+                    PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html")
+                    Task.Progress = 0.8
+                    Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
+                    Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources(0))
+                Catch ex As Exception
+                    Log(ex, "获取 OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址失败")
+                End Try
+                'OptiFine 中文镜像源
+                Sources.Add("https://optifine.cn/download/" & DownloadInfo.NameFile)
+                '构造文件请求
+                Task.Output = New List(Of NetFile) From {New NetFile(Sources.ToArray, Target, New FileChecker(MinSize:=64 * 1024))}
+            End Sub) With {.ProgressWeight = 8})
         Loaders.Add(New LoaderDownload("下载 OptiFine 主文件", New List(Of NetFile)) With {.ProgressWeight = 8})
         Loaders.Add(New LoaderDownload("下载 OptiFine 主文件", New List(Of NetFile)) With {.ProgressWeight = 8})
         Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("等待原版下载",
         Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("等待原版下载",
-                                                                         Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
-                                                                             '等待原版文件下载完成
-                                                                             If ClientDownloadLoader Is Nothing Then Exit Sub
-                                                                             Dim TargetLoaders As List(Of LoaderBase) =
-                                                                                ClientDownloadLoader.GetLoaderList.Where(Function(l) l.Name = McDownloadClientLibName OrElse l.Name = McDownloadClientJsonName).
-                                                                                Where(Function(l) l.State <> LoadState.Finished).ToList
-                                                                             If TargetLoaders.Count > 0 Then Log("[Download] OptiFine 安装正在等待原版文件下载完成")
-                                                                             Do While TargetLoaders.Count > 0 AndAlso Not Task.IsAborted
-                                                                                 TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList
-                                                                                 Thread.Sleep(50)
-                                                                             Loop
-                                                                             If Task.IsAborted Then Exit Sub
-                                                                             '拷贝原版文件
-                                                                             If Not IsCustomFolder Then Exit Sub
-                                                                             SyncLock VanillaSyncLock
-                                                                                 Dim ClientName As String = GetFolderNameFromPath(ClientFolder)
-                                                                                 Directory.CreateDirectory(McFolder & "versions\" & DownloadInfo.Inherit)
-                                                                                 If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json") Then
-                                                                                     CopyFile(ClientFolder & ClientName & ".json",
+            Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
+                '等待原版文件下载完成
+                If ClientDownloadLoader Is Nothing Then Exit Sub
+                Dim TargetLoaders As List(Of LoaderBase) =
+                   ClientDownloadLoader.GetLoaderList.Where(Function(l) l.Name = McDownloadClientLibName OrElse l.Name = McDownloadClientJsonName).
+                   Where(Function(l) l.State <> LoadState.Finished).ToList
+                If TargetLoaders.Count > 0 Then Log("[Download] OptiFine 安装正在等待原版文件下载完成")
+                Do While TargetLoaders.Count > 0 AndAlso Not Task.IsAborted
+                    TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList
+                    Thread.Sleep(50)
+                Loop
+                If Task.IsAborted Then Exit Sub
+                '拷贝原版文件
+                If Not IsCustomFolder Then Exit Sub
+                SyncLock VanillaSyncLock
+                    Dim ClientName As String = GetFolderNameFromPath(ClientFolder)
+                    Directory.CreateDirectory(McFolder & "versions\" & DownloadInfo.Inherit)
+                    If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json") Then
+                        CopyFile(ClientFolder & ClientName & ".json",
                                                                                           McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json")
                                                                                           McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json")
-                                                                                 End If
-                                                                                 If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar") Then
-                                                                                     CopyFile(ClientFolder & ClientName & ".jar",
+                    End If
+                    If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar") Then
+                        CopyFile(ClientFolder & ClientName & ".jar",
                                                                                               McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar")
                                                                                               McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar")
-                                                                                 End If
-                                                                             End SyncLock
-                                                                         End Sub) With {.ProgressWeight = 0.1, .Show = False})
+                    End If
+                End SyncLock
+            End Sub) With {.ProgressWeight = 0.1, .Show = False})
 
 
         '安装(新旧方式均需要原版 Jar 和 Json)
         '安装(新旧方式均需要原版 Jar 和 Json)
         If IsNewVersion Then
         If IsNewVersion Then
@@ -586,7 +590,19 @@ Public Module ModDownloadLib
                               BaseMcFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar")
                               BaseMcFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar")
                     Task.Progress = 0.06
                     Task.Progress = 0.06
                     '进行安装
                     '进行安装
-                    McDownloadOptiFineInstall(BaseMcFolderHome, Target, Task)
+                    Dim UseJavaWrapper As Boolean = True
+Retry:
+                    Try
+                        McDownloadOptiFineInstall(BaseMcFolderHome, Target, Task, UseJavaWrapper)
+                    Catch ex As Exception
+                        If UseJavaWrapper Then
+                            Log(ex, "使用 JavaWrapper 安装 OptiFine 失败,将不使用 JavaWrapper 并重试")
+                            UseJavaWrapper = False
+                            GoTo Retry
+                        Else
+                            Throw New Exception("运行 OptiFine 安装器失败", ex)
+                        End If
+                    End Try
                     Task.Progress = 0.96
                     Task.Progress = 0.96
                     '复制文件
                     '复制文件
                     File.Delete(BaseMcFolder & "launcher_profiles.json")
                     File.Delete(BaseMcFolder & "launcher_profiles.json")
@@ -596,7 +612,7 @@ Public Module ModDownloadLib
                     File.Delete(Target)
                     File.Delete(Target)
                     DeleteDirectory(BaseMcFolderHome)
                     DeleteDirectory(BaseMcFolderHome)
                 Catch ex As Exception
                 Catch ex As Exception
-                    Throw New Exception("安装新版 OptiFine 版本失败", ex)
+                    Throw New Exception("安装 OptiFine(方式 A)失败", ex)
                 End Try
                 End Try
             End Sub) With {.ProgressWeight = 8})
             End Sub) With {.ProgressWeight = 8})
         Else
         Else
@@ -645,7 +661,7 @@ Public Module ModDownloadLib
                     End If
                     End If
                     WriteFile(VersionFolder & Id & ".json", Json)
                     WriteFile(VersionFolder & Id & ".json", Json)
                 Catch ex As Exception
                 Catch ex As Exception
-                    Throw New Exception("安装旧版 OptiFine 版本失败", ex)
+                    Throw New Exception("安装 OptiFine(方式 B)失败", ex)
                 End Try
                 End Try
             End Sub) With {.ProgressWeight = 1})
             End Sub) With {.ProgressWeight = 1})
         End If
         End If
@@ -1110,7 +1126,7 @@ Public Module ModDownloadLib
         End Try
         End Try
     End Sub
     End Sub
 
 
-    Private Sub ForgeInjector(Target As String, Task As LoaderTask(Of Boolean, Boolean), McFolder As String)
+    Private Sub ForgeInjector(Target As String, Task As LoaderTask(Of Boolean, Boolean), McFolder As String, UseJavaWrapper As Boolean)
         '选择 Java
         '选择 Java
         Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
         Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
         If Java Is Nothing Then
         If Java Is Nothing Then
@@ -1127,7 +1143,12 @@ Public Module ModDownloadLib
             If Java Is Nothing Then Throw New Exception("由于未找到 Java,已取消安装。")
             If Java Is Nothing Then Throw New Exception("由于未找到 Java,已取消安装。")
         End If
         End If
         '添加 Java Wrapper 作为主 Jar
         '添加 Java Wrapper 作为主 Jar
-        Dim Arguments = "-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """ -cp """ & PathTemp & "Cache\forge_installer.jar;" & Target & """ -jar """ & ExtractJavaWrapper() & """  com.bangbang93.ForgeInstaller """ & McFolder
+        Dim Arguments As String
+        If UseJavaWrapper Then
+            Arguments = $"-Doolloo.jlw.tmpdir=""{GetJavaWrapperDir()}"" -cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" -jar ""{ExtractJavaWrapper()}"" com.bangbang93.ForgeInstaller ""{McFolder}"
+        Else
+            Arguments = $"-cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" com.bangbang93.ForgeInstaller ""{McFolder}"
+        End If
         If Java.VersionCode >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments
         If Java.VersionCode >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments
         '开始启动
         '开始启动
         SyncLock InstallSyncLock
         SyncLock InstallSyncLock
@@ -1415,12 +1436,25 @@ Public Module ModDownloadLib
                                                                     '释放 launcher_installer.json
                                                                     '释放 launcher_installer.json
                                                                     McFolderLauncherProfilesJsonCreate(McFolder)
                                                                     McFolderLauncherProfilesJsonCreate(McFolder)
                                                                     Task.Progress = 0.05
                                                                     Task.Progress = 0.05
-                                                                    '释放 Forge 注入器
-                                                                    WriteFile(PathTemp & "Cache\forge_installer.jar", GetResources("ForgeInstaller"))
-                                                                    Task.Progress = 0.06
-                                                                    '运行注入器
-                                                                    ForgeInjector(InstallerAddress, Task, McFolder)
-                                                                    Task.Progress = 0.97
+                                                                    '运行 Forge 安装器
+                                                                    Dim UseJavaWrapper As Boolean = True
+Retry:
+                                                                    Try
+                                                                        '释放 Forge 注入器
+                                                                        WriteFile(PathTemp & "Cache\forge_installer.jar", GetResources("ForgeInstaller"))
+                                                                        Task.Progress = 0.06
+                                                                        '运行注入器
+                                                                        ForgeInjector(InstallerAddress, Task, McFolder, UseJavaWrapper)
+                                                                        Task.Progress = 0.97
+                                                                    Catch ex As Exception
+                                                                        If UseJavaWrapper Then
+                                                                            Log(ex, "使用 JavaWrapper 安装 Forge 失败,将不使用 JavaWrapper 并重试")
+                                                                            UseJavaWrapper = False
+                                                                            GoTo Retry
+                                                                        Else
+                                                                            Throw New Exception("运行 Forge 安装器失败", ex)
+                                                                        End If
+                                                                    End Try
                                                                     '拷贝新增的版本 Json
                                                                     '拷贝新增的版本 Json
                                                                     Dim DeltaList = New DirectoryInfo(McFolder & "versions\").EnumerateDirectories.SkipWhile(Function(Info As DirectoryInfo) As Boolean
                                                                     Dim DeltaList = New DirectoryInfo(McFolder & "versions\").EnumerateDirectories.SkipWhile(Function(Info As DirectoryInfo) As Boolean
                                                                                                                                                                  Return OldList.Contains(Info.FullName)
                                                                                                                                                                  Return OldList.Contains(Info.FullName)
@@ -1470,7 +1504,7 @@ Public Module ModDownloadLib
                                                                         Task.Progress = 0.6
                                                                         Task.Progress = 0.6
                                                                         '解压支持库文件
                                                                         '解压支持库文件
                                                                         Installer.Dispose()
                                                                         Installer.Dispose()
-                                                                        ZipFile.ExtractToDirectory(InstallerAddress, InstallerAddress & "_unrar\")
+                                                                        ExtractFile(InstallerAddress, InstallerAddress & "_unrar\")
                                                                         CopyDirectory(InstallerAddress & "_unrar\maven\", McFolder & "libraries\")
                                                                         CopyDirectory(InstallerAddress & "_unrar\maven\", McFolder & "libraries\")
                                                                         DeleteDirectory(InstallerAddress & "_unrar\")
                                                                         DeleteDirectory(InstallerAddress & "_unrar\")
                                                                     Else
                                                                     Else

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

@@ -848,7 +848,7 @@
         If LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "正在获取版本列表……"
         If LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "正在获取版本列表……"
         If LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadFabric.State, Object).Error.Message
         If LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadFabric.State, Object).Error.Message
         For Each Version As JObject In DlFabricListLoader.Output.Value("game")
         For Each Version As JObject In DlFabricListLoader.Output.Value("game")
-            If Version("version").ToString = SelectedMinecraftId.Replace("∞", "infinite") Then
+            If Version("version").ToString = SelectedMinecraftId.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3") Then
                 If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容"
                 If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容"
                 'If SelectedOptiFine IsNot Nothing Then Return "与 OptiFine 不兼容"
                 'If SelectedOptiFine IsNot Nothing Then Return "与 OptiFine 不兼容"
                 Return Nothing
                 Return Nothing
@@ -908,7 +908,7 @@
     Public Shared Function IsSuitableFabricApi(DisplayName As String, MinecraftVersion As String) As Boolean
     Public Shared Function IsSuitableFabricApi(DisplayName As String, MinecraftVersion As String) As Boolean
         Try
         Try
             If DisplayName Is Nothing OrElse MinecraftVersion Is Nothing Then Return False
             If DisplayName Is Nothing OrElse MinecraftVersion Is Nothing Then Return False
-            DisplayName = DisplayName.ToLower : MinecraftVersion = MinecraftVersion.ToLower
+            DisplayName = DisplayName.ToLower : MinecraftVersion = MinecraftVersion.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3").ToLower
             If DisplayName.StartsWith("[" & MinecraftVersion & "]") Then Return True
             If DisplayName.StartsWith("[" & MinecraftVersion & "]") Then Return True
             If Not DisplayName.Contains("/") OrElse Not DisplayName.Contains("]") Then Return False
             If Not DisplayName.Contains("/") OrElse Not DisplayName.Contains("]") Then Return False
             '直接的判断(例如 1.18.1/22w03a)
             '直接的判断(例如 1.18.1/22w03a)

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

@@ -104,7 +104,7 @@
                    (Image.Pic.GetPixel(1, 1) <> Image.Pic.GetPixel(Scale * 41, Scale * 9) AndAlso '或是头部颜色和透明区均不一样
                    (Image.Pic.GetPixel(1, 1) <> Image.Pic.GetPixel(Scale * 41, Scale * 9) AndAlso '或是头部颜色和透明区均不一样
                     Image.Pic.GetPixel(Image.Pic.Width - 1, Image.Pic.Height - 1) <> Image.Pic.GetPixel(Scale * 41, Scale * 9) AndAlso
                     Image.Pic.GetPixel(Image.Pic.Width - 1, Image.Pic.Height - 1) <> Image.Pic.GetPixel(Scale * 41, Scale * 9) AndAlso
                     Image.Pic.GetPixel(Image.Pic.Width - 2, Image.Pic.Height / 2 - 2) <> Image.Pic.GetPixel(Scale * 41, Scale * 9)) Then
                     Image.Pic.GetPixel(Image.Pic.Width - 2, Image.Pic.Height / 2 - 2) <> Image.Pic.GetPixel(Scale * 41, Scale * 9)) Then
-                    ImgFore.Source = New CroppedBitmap(Image, New Int32Rect(Scale * 40, Scale * 8, Scale * 8, Scale * 8))
+                    ImgFore.Source = Image.Clip(Scale * 40, Scale * 8, Scale * 8, Scale * 8)
                 Else
                 Else
                     ImgFore.Source = Nothing
                     ImgFore.Source = Nothing
                 End If
                 End If
@@ -112,7 +112,7 @@
                 ImgFore.Source = Nothing
                 ImgFore.Source = Nothing
             End If
             End If
             '脸层
             '脸层
-            ImgBack.Source = New CroppedBitmap(Image, New Int32Rect(Scale * 8, Scale * 8, Scale * 8, Scale * 8))
+            ImgBack.Source = Image.Clip(Scale * 8, Scale * 8, Scale * 8, Scale * 8)
             Log("[Skin] 载入头像成功:" & Loader.Name)
             Log("[Skin] 载入头像成功:" & Loader.Name)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "载入头像失败(" & If(Address, "null") & "," & Loader.Name & ")", LogLevel.Hint)
             Log(ex, "载入头像失败(" & If(Address, "null") & "," & Loader.Name & ")", LogLevel.Hint)

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

@@ -673,14 +673,7 @@ ExitRefresh:
         McLaunchProcess = Nothing
         McLaunchProcess = Nothing
         McLaunchWatcher = Nothing
         McLaunchWatcher = Nothing
         '获取 “你知道吗” 提示
         '获取 “你知道吗” 提示
-        Dim Hint As String = PageOtherTest.GetRandomHint()
-        Try
-            Dim RawHints = ReadFile(Path & "PCL\hints.txt").Split(vbCrLf.ToCharArray).Where(Function(s) Not String.IsNullOrWhiteSpace(s)).ToList
-            If RawHints.Count > 0 Then Hint = RandomOne(RawHints)
-        Catch ex As Exception
-            Log(ex, "获取自定义 你知道吗 提示失败", LogLevel.Hint)
-        End Try
-        LabLaunchingHint.Text = Hint
+        LabLaunchingHint.Text = PageOtherTest.GetRandomHint()
     End Sub
     End Sub
     Private ShowProgress As Double = 0
     Private ShowProgress As Double = 0
     ''' <summary>
     ''' <summary>

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

@@ -133,7 +133,7 @@ StartDownload:
         Log("[IOI] 解压联机模块以完成下载")
         Log("[IOI] 解压联机模块以完成下载")
         If File.Exists(PathTemp & "ioi.exe") Then File.Delete(PathTemp & "ioi.exe")
         If File.Exists(PathTemp & "ioi.exe") Then File.Delete(PathTemp & "ioi.exe")
         If File.Exists(PathIoi) Then File.Delete(PathIoi)
         If File.Exists(PathIoi) Then File.Delete(PathIoi)
-        Compression.ZipFile.ExtractToDirectory(PathTemp & "联机模块.zip", PathTemp)
+        ExtractFile(PathTemp & "联机模块.zip", PathTemp)
         File.Delete(PathTemp & "联机模块.zip")
         File.Delete(PathTemp & "联机模块.zip")
         CopyFile(PathTemp & "ioi.exe", PathIoi)
         CopyFile(PathTemp & "ioi.exe", PathIoi)
         File.Delete(PathTemp & "ioi.exe")
         File.Delete(PathTemp & "ioi.exe")

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

@@ -14,7 +14,7 @@
             If String.IsNullOrEmpty(Entry.XamlContent) Then Throw New Exception("帮助 xaml 文件为空")
             If String.IsNullOrEmpty(Entry.XamlContent) Then Throw New Exception("帮助 xaml 文件为空")
             Me.Entry = Entry
             Me.Entry = Entry
             PanCustom.Children.Clear()
             PanCustom.Children.Clear()
-            FileContent = FileContent.Replace("{path}", Path)
+            FileContent = HelpArgumentReplace(FileContent)
             PanCustom.Children.Add(GetObjectFromXML(FileContent))
             PanCustom.Children.Add(GetObjectFromXML(FileContent))
             Return True
             Return True
         Catch ex As Exception
         Catch ex As Exception

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

@@ -219,6 +219,7 @@
                         If(IsHintIndie, vbCrLf & "由于该版本开启了版本隔离,删除版本时该版本对应的存档、资源包、Mod 等文件也将被一并删除!", ""),
                         If(IsHintIndie, vbCrLf & "由于该版本开启了版本隔离,删除版本时该版本对应的存档、资源包、Mod 等文件也将被一并删除!", ""),
                         "版本删除确认", , "取消",, True)
                         "版本删除确认", , "取消",, True)
                 Case 1
                 Case 1
+                    IniClearCache(Version.Path & "PCL\Setup.ini")
                     If IsShiftPressed Then
                     If IsShiftPressed Then
                         DeleteDirectory(Version.Path)
                         DeleteDirectory(Version.Path)
                         Hint("版本 " & Version.Name & " 已永久删除!", HintType.Finish)
                         Hint("版本 " & Version.Name & " 已永久删除!", HintType.Finish)

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

@@ -74,7 +74,7 @@
                     <local:MyCheckBox Text="正式版更新提示" Grid.Column="1" x:Name="CheckUpdateRelease" Tag="ToolUpdateRelease" ToolTip="在 Minecraft 正式版更新时弹窗提示" />
                     <local:MyCheckBox Text="正式版更新提示" Grid.Column="1" x:Name="CheckUpdateRelease" Tag="ToolUpdateRelease" ToolTip="在 Minecraft 正式版更新时弹窗提示" />
                     <local:MyCheckBox Text="测试版更新提示" Grid.Column="2" Height="22" x:Name="CheckUpdateSnapshot" Tag="ToolUpdateSnapshot" ToolTip="在 Minecraft 测试版(快照、预发布版)更新时弹窗提示" />
                     <local:MyCheckBox Text="测试版更新提示" Grid.Column="2" Height="22" x:Name="CheckUpdateSnapshot" Tag="ToolUpdateSnapshot" ToolTip="在 Minecraft 测试版(快照、预发布版)更新时弹窗提示" />
                     <TextBlock VerticalAlignment="Center" Grid.Row="2" HorizontalAlignment="Left" Text="游戏语言" Margin="0,0,25,0" />
                     <TextBlock VerticalAlignment="Center" Grid.Row="2" HorizontalAlignment="Left" Text="游戏语言" Margin="0,0,25,0" />
-                    <local:MyCheckBox Text="默认设置为中文" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="5" x:Name="CheckHelpChinese" Tag="ToolHelpChinese" />
+                    <local:MyCheckBox Text="自动设置为中文" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="5" x:Name="CheckHelpChinese" Tag="ToolHelpChinese" />
                 </Grid>
                 </Grid>
             </local:MyCard>
             </local:MyCard>
             <local:MyCard Margin="0,0,0,15" Title="系统">
             <local:MyCard Margin="0,0,0,15" Title="系统">

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

@@ -195,7 +195,6 @@
                                                  AniDispose(sender, False)
                                                  AniDispose(sender, False)
                                                  AniDispose(Card, True, Sub() If FrmSpeedRight.PanMain.Children.Count = 0 AndAlso FrmMain.PageCurrent = FormMain.PageType.DownloadManager Then FrmMain.PageBack())
                                                  AniDispose(Card, True, Sub() If FrmSpeedRight.PanMain.Children.Count = 0 AndAlso FrmMain.PageCurrent = FormMain.PageType.DownloadManager Then FrmMain.PageBack())
                                                  RightCards.Remove(Loader.Name)
                                                  RightCards.Remove(Loader.Name)
-                                                 LoaderTaskbar.Remove(Loader)
                                                  Log($"[Watcher] 关闭下载管理卡片:{Loader.Name}")
                                                  Log($"[Watcher] 关闭下载管理卡片:{Loader.Name}")
                                                  RunInThread(Sub() Loader.Abort())
                                                  RunInThread(Sub() Loader.Abort())
                                              End Sub
                                              End Sub

+ 185 - 127
Plain Craft Launcher 2/Resources/Custom.xaml

@@ -4,102 +4,102 @@
      你也可以使用 Ctrl + F 快速查找。例如,若需要改变文本颜色,则搜索 “颜色” 即可。 -->
      你也可以使用 Ctrl + F 快速查找。例如,若需要改变文本颜色,则搜索 “颜色” 即可。 -->
 
 
 <local:MyCard Title="纯文本" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="纯文本" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
+	<StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="每个 local:MyCard 代表一张卡片,你可以添加、删除格式类似的 MyCard 来添加多个卡片。每个 TextBlock 代表一段文本,你可以在 Text 属性中书写任何你想写的内容,也可以自行添加更多的 TextBlock。" />
                     Text="每个 local:MyCard 代表一张卡片,你可以添加、删除格式类似的 MyCard 来添加多个卡片。每个 TextBlock 代表一段文本,你可以在 Text 属性中书写任何你想写的内容,也可以自行添加更多的 TextBlock。" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4" FontSize="11"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4" FontSize="11"
                     Text="你可以通过添加、删除属性修改样式,例如上一行的 FontSize 就会将字号改为 11 号。" />
                     Text="你可以通过添加、删除属性修改样式,例如上一行的 FontSize 就会将字号改为 11 号。" />
         <TextBlock TextWrapping="Wrap" Margin="20,5,20,0" Foreground="#8C7721"
         <TextBlock TextWrapping="Wrap" Margin="20,5,20,0" Foreground="#8C7721"
                     Text="它还有许多可以调整的属性:上一行的 Margin 调整了边距,Foreground 则会让文字变色。" />
                     Text="它还有许多可以调整的属性:上一行的 Margin 调整了边距,Foreground 则会让文字变色。" />
-    </StackPanel>
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="卡片、提示条" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="卡片、提示条" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
-        <local:MyHint Text="local:MyHint 代表提示条,它默认是警告样式。" />
-        <local:MyHint Margin="0,8,0,2" IsWarn="False"
+	<StackPanel Margin="25,40,23,15">
+		<local:MyHint Text="local:MyHint 代表提示条,它默认是警告样式。" />
+		<local:MyHint Margin="0,8,0,2" IsWarn="False"
                     Text="将提示条的 IsWarn 属性改为 False 即可改为提示样式。&#xA;使用左边的那堆字符可以在任意地方手动换行,如果需要使用等号、引号等特殊字符,可以自行百度 XAML 转义字符。" />
                     Text="将提示条的 IsWarn 属性改为 False 即可改为提示样式。&#xA;使用左边的那堆字符可以在任意地方手动换行,如果需要使用等号、引号等特殊字符,可以自行百度 XAML 转义字符。" />
         <TextBlock TextWrapping="Wrap" Margin="0,6,0,0"
         <TextBlock TextWrapping="Wrap" Margin="0,6,0,0"
                     Text="卡片(local:MyCard)的 Title 属性决定了它的标题。&#xA;为卡片添加 CanSwap 属性让它可以被折叠,True 代表是,False 代表否。在此基础上,再使用 IsSwaped 属性调整它是否默认被折叠。" />
                     Text="卡片(local:MyCard)的 Title 属性决定了它的标题。&#xA;为卡片添加 CanSwap 属性让它可以被折叠,True 代表是,False 代表否。在此基础上,再使用 IsSwaped 属性调整它是否默认被折叠。" />
-    </StackPanel>
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="长宽属性、图片" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="长宽属性、图片" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
+	<StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4" Width="320" HorizontalAlignment="Right"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4" Width="320" HorizontalAlignment="Right"
                     Text="任意项目都可以添加 Width 与 Height 属性来控制宽高。HorizontalAlignment 属性可以控制对齐:Center 代表居中,Right 代表居右,例如这段文本就被居右了。" />
                     Text="任意项目都可以添加 Width 与 Height 属性来控制宽高。HorizontalAlignment 属性可以控制对齐:Center 代表居中,Right 代表居右,例如这段文本就被居右了。" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,6"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,6"
                     Text="Image 代表图片,你需要在它的 Source 属性中填写一个网址或文件路径,它会从该处获取图片并显示。通常需要使用 Height 限制它的高度。" />
                     Text="Image 代表图片,你需要在它的 Source 属性中填写一个网址或文件路径,它会从该处获取图片并显示。通常需要使用 Height 限制它的高度。" />
-        <Image Height="50" HorizontalAlignment="Center" Source="https://www.mcbbs.net/template/mcbbs/image/logo_sc.png" />
-    </StackPanel>
+		<Image Height="50" HorizontalAlignment="Center" Source="https://www.mcbbs.net/template/mcbbs/image/logo_sc.png" />
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="按钮" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="按钮" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
+	<StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="添加 local:MyButton 即可新建一个按钮。你需要限定它的尺寸与位置,并通过 Padding 属性进一步控制它的内边距。" />
                     Text="添加 local:MyButton 即可新建一个按钮。你需要限定它的尺寸与位置,并通过 Padding 属性进一步控制它的内边距。" />
-        <local:MyButton Margin="0,4,0,10" Height="35" HorizontalAlignment="Left" Padding="25,0,25,0"
+		<local:MyButton Margin="0,4,0,10" Height="35" HorizontalAlignment="Left" Padding="25,0,25,0"
                     Text="这是一个按钮!一个按钮!" />
                     Text="这是一个按钮!一个按钮!" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="你可以修改 ColorType 属性来调整按钮配色:Highlight 代表当前主题色,Red 代表红色。ToolTip 属性还可以让你在鼠标悬浮在上面的时候显示提示文本。" />
                     Text="你可以修改 ColorType 属性来调整按钮配色:Highlight 代表当前主题色,Red 代表红色。ToolTip 属性还可以让你在鼠标悬浮在上面的时候显示提示文本。" />
-        <local:MyButton Margin="0,4,0,10" Width="250" Height="35" ColorType="Highlight"
+		<local:MyButton Margin="0,4,0,10" Width="250" Height="35" ColorType="Highlight"
                     Text="把鼠标停在这里别动……" ToolTip="……就能看到这句话!" />
                     Text="把鼠标停在这里别动……" ToolTip="……就能看到这句话!" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="local:MyTextButton 是没有边框的 “纯文本按钮”,其作用与 local:MyButton 一致。" />
                     Text="local:MyTextButton 是没有边框的 “纯文本按钮”,其作用与 local:MyButton 一致。" />
-        <local:MyTextButton Margin="0,8,0,10" HorizontalAlignment="Center"
+		<local:MyTextButton Margin="0,8,0,10" HorizontalAlignment="Center"
                     Text="一个精简版的文本按钮" />
                     Text="一个精简版的文本按钮" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="按钮可以做到很多事,具体将在 “事件” 部分介绍。" />
                     Text="按钮可以做到很多事,具体将在 “事件” 部分介绍。" />
-    </StackPanel>
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="事件 1:打开网页、弹出窗口" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="事件 1:打开网页、弹出窗口" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
+	<StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
                     Text="将按钮的 EventType 属性设为 打开网页,然后在 EventData 属性中写入网址,即可通过点击按钮打开网页。" />
                     Text="将按钮的 EventType 属性设为 打开网页,然后在 EventData 属性中写入网址,即可通过点击按钮打开网页。" />
-        <local:MyButton Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
+		<local:MyButton Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
                     Text="打开 MCBBS" EventType="打开网页" EventData="https://www.mcbbs.net/" />
                     Text="打开 MCBBS" EventType="打开网页" EventData="https://www.mcbbs.net/" />
         <TextBlock TextWrapping="Wrap" Margin="0,10,0,10"
         <TextBlock TextWrapping="Wrap" Margin="0,10,0,10"
                     Text="或者将 EventType 属性改为 弹出窗口,然后在 EventData 属性中写入弹窗的标题与内容……" />
                     Text="或者将 EventType 属性改为 弹出窗口,然后在 EventData 属性中写入弹窗的标题与内容……" />
-        <local:MyButton Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
+		<local:MyButton Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
                     Text="显示一个弹窗" EventType="弹出窗口" EventData="这是标题|标题与内容以竖线间隔。\n你也可以直接在其中换行。" />
                     Text="显示一个弹窗" EventType="弹出窗口" EventData="这是标题|标题与内容以竖线间隔。\n你也可以直接在其中换行。" />
         <TextBlock TextWrapping="Wrap" Margin="0,10,0,0"
         <TextBlock TextWrapping="Wrap" Margin="0,10,0,0"
                     Text="local:MyTextButton 同样可以设置 EventType 与 EventData 让它具有特定功能。" />
                     Text="local:MyTextButton 同样可以设置 EventType 与 EventData 让它具有特定功能。" />
-        <local:MyTextButton Margin="0,8,0,0" HorizontalAlignment="Center"
+		<local:MyTextButton Margin="0,8,0,0" HorizontalAlignment="Center"
                     Text="点击打开知乎" EventType="打开网页" EventData="https://www.zhihu.com/" />
                     Text="点击打开知乎" EventType="打开网页" EventData="https://www.zhihu.com/" />
-    </StackPanel>
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="事件 2:启动游戏" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="事件 2:启动游戏" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
+	<StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="将 EventType 设置为 启动游戏,EventData 设置为具体的游戏版本,即可通过点击按钮启动游戏。如果当前游戏文件夹没有该版本,则无法启动。" />
                     Text="将 EventType 设置为 启动游戏,EventData 设置为具体的游戏版本,即可通过点击按钮启动游戏。如果当前游戏文件夹没有该版本,则无法启动。" />
-        <local:MyButton Margin="0,4,0,10" Width="250" Height="35"
+		<local:MyButton Margin="0,4,0,10" Width="250" Height="35"
                     Text="启动 Minecraft 1.12.2" EventType="启动游戏" EventData="1.12.2" />
                     Text="启动 Minecraft 1.12.2" EventType="启动游戏" EventData="1.12.2" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="在 EventData 后面添加一条竖线(|),竖线后填写服务器 IP,即可在启动该版本的同时自动进入服务器。" />
                     Text="在 EventData 后面添加一条竖线(|),竖线后填写服务器 IP,即可在启动该版本的同时自动进入服务器。" />
-        <local:MyButton Margin="0,4,0,0" Width="250" Height="35"
+		<local:MyButton Margin="0,4,0,0" Width="250" Height="35"
                     Text="启动 1.12.2 并进入 Hypixel" EventType="启动游戏" EventData="1.12.2|mc.hypixel.net" ToolTip="服务器 IP:mc.hypixel.net" />
                     Text="启动 1.12.2 并进入 Hypixel" EventType="启动游戏" EventData="1.12.2|mc.hypixel.net" ToolTip="服务器 IP:mc.hypixel.net" />
-    </StackPanel>
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="事件 3:打开文件" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="事件 3:打开文件" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
+	<StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="将 EventType 设置为 打开文件,EventData 设置为文件路径,即可点击打开特定文件或启动程序。" />
                     Text="将 EventType 设置为 打开文件,EventData 设置为文件路径,即可点击打开特定文件或启动程序。" />
-        <local:MyButton Margin="0,4,0,8" Width="200" Height="35"
+		<local:MyButton Margin="0,4,0,8" Width="200" Height="35"
                     Text="打开记事本" EventType="打开文件" EventData="notepad.exe" />
                     Text="打开记事本" EventType="打开文件" EventData="notepad.exe" />
         <TextBlock TextWrapping="Wrap" Margin="0,4,0,0"
         <TextBlock TextWrapping="Wrap" Margin="0,4,0,0"
                     Text="如果能在 PCL 文件夹中找到该文件,则会优先打开 PCL 文件夹中的文件。当然,你可以使用例如 C:\ 开头的绝对路径。" />
                     Text="如果能在 PCL 文件夹中找到该文件,则会优先打开 PCL 文件夹中的文件。当然,你可以使用例如 C:\ 开头的绝对路径。" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
-                    Text="此外,PCL2 会将文件中的花括号 path 替换为 PCL 可执行文件所在文件夹({path}),可以用于执行特定程序或加载图片。" />
-        <local:MyButton Margin="0,4,0,10" Width="200" Height="35"
-                    Text="打开 PCL 日志" EventType="打开文件" EventData="Log1.txt" ToolTip="PCL2 的日志文件就在 PCL 文件夹内,且名为 Log1.txt" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+                    Text="此外,PCL 会将文件中的 “花括号 path” 替换为 PCL 可执行文件所在文件夹({path}),可以用于执行特定程序或加载图片。" />
+		<local:MyButton Margin="0,4,0,10" Width="200" Height="35"
+                    Text="打开 PCL 日志" EventType="打开文件" EventData="Log1.txt" ToolTip="PCL 的日志文件就在 PCL 文件夹内,且名为 Log1.txt" />
+		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="就像为游戏指定服务器 IP 一样,你也可以在 EventData 的竖线后为程序添加启动参数。" />
                     Text="就像为游戏指定服务器 IP 一样,你也可以在 EventData 的竖线后为程序添加启动参数。" />
-        <local:MyButton Margin="0,4,0,0" Width="200" Height="35"
-                    Text="定位游戏启动脚本" EventType="打开文件" EventData="explorer.exe|/select,LatestLaunch.bat" ToolTip="PCL2 会将上一次启动 MC 的脚本保存在 PCL 文件夹内,且名为 LatestLaunch.bat。&#xA;点击按钮会打开文件管理器,并且借助参数要求它选中这个文件。" />
-    </StackPanel>
+		<local:MyButton Margin="0,4,0,0" Width="200" Height="35"
+                    Text="定位游戏启动脚本" EventType="打开文件" EventData="explorer.exe|/select,LatestLaunch.bat" ToolTip="PCL 会将上一次启动 MC 的脚本保存在 PCL 文件夹内,且名为 LatestLaunch.bat。&#xA;点击按钮会打开文件管理器,并且借助参数要求它选中这个文件。" />
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="事件 4:其他事件类型" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="事件 4:其他事件类型" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
@@ -133,128 +133,176 @@
 	</StackPanel>
 	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
-<local:MyCard Title="横向布局 1:StackPanel" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
+<local:MyCard Title="StackPanel 横向布局" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+	<StackPanel Margin="25,40,23,15">
+		<TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
                     Text="你需要使用 StackPanel 在一行里塞下多个按钮。你可以仅在这个教学卡片的基础上稍作调整,来实现自己的按钮布局。" />
                     Text="你需要使用 StackPanel 在一行里塞下多个按钮。你可以仅在这个教学卡片的基础上稍作调整,来实现自己的按钮布局。" />
-        <!-- 这是第一行 -->
-        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
-            <local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0" ColorType="Highlight"
+		<!-- 这是第一行 -->
+		<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+			<local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0" ColorType="Highlight"
                         Text="打开 B 站" EventType="打开网页" EventData="https://www.bilibili.com/" />
                         Text="打开 B 站" EventType="打开网页" EventData="https://www.bilibili.com/" />
-            <local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
+			<local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
                         Text="进入 Hypixel" EventType="启动游戏" EventData="1.16.3|mc.hypixel.net" ToolTip="使用 Minecraft 1.16.3 进入 Hypixel" />
                         Text="进入 Hypixel" EventType="启动游戏" EventData="1.16.3|mc.hypixel.net" ToolTip="使用 Minecraft 1.16.3 进入 Hypixel" />
-            <local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
+			<local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
                         Text="打开画图" EventType="打开文件" EventData="mspaint.exe" />
                         Text="打开画图" EventType="打开文件" EventData="mspaint.exe" />
-        </StackPanel>
-        <!-- 这是第二行 -->
-        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,10,0,0">
-            <local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0" ColorType="Red"
+		</StackPanel>
+		<!-- 这是第二行 -->
+		<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,10,0,0">
+			<local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0" ColorType="Red"
                         Text="复制召唤命令" EventType="复制文本" EventData="/execute as @a at @s run summon Creeper ~ ~2 ~" ToolTip="这条 Minecraft 命令会在每个玩家头顶召唤一只苦力怕!" />
                         Text="复制召唤命令" EventType="复制文本" EventData="/execute as @a at @s run summon Creeper ~ ~2 ~" ToolTip="这条 Minecraft 命令会在每个玩家头顶召唤一只苦力怕!" />
-            <local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
+			<local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
                         Text="内存优化" EventType="内存优化" />
                         Text="内存优化" EventType="内存优化" />
-            <local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
+			<local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
                         Text="下载作者的头像" EventType="下载文件" EventData="https://www.mcbbs.net/uc_server/data/avatar/000/14/51/06_avatar_small.jpg?ts=1471402592" />
                         Text="下载作者的头像" EventType="下载文件" EventData="https://www.mcbbs.net/uc_server/data/avatar/000/14/51/06_avatar_small.jpg?ts=1471402592" />
-        </StackPanel>
-    </StackPanel>
-</local:MyCard>
-
-<local:MyCard Title="横向布局 2:Grid" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
-                    Text="如果要实现更复杂的布局,则必须使用 Grid。Grid 可以让按钮们自动适应窗口宽度:随意拉伸 PCL 窗口,按钮大小会自动改变。" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
-                    Text="你可以上网查找 “WPF Grid” 的相关教程,这里仅给出一个例子,不作详细介绍。你可以仅在这个教学卡片的基础上稍作调整,来实现自己的按钮布局。" />
-        <Grid>
-            <Grid.ColumnDefinitions>
-                <ColumnDefinition Width="1*" />
-                <ColumnDefinition Width="1.6*" />
-                <!-- 这个按钮的宽度是其他按钮的 1.6 倍 -->
-                <ColumnDefinition Width="1*" />
-                <!-- 如果不打 *,则会占用固定的宽度 -->
-            </Grid.ColumnDefinitions>
-            <!-- 为按钮添加 Grid.Column 属性,删除 Width 与 HorizontalAlignment 属性 -->
-            <local:MyButton Grid.Column="0" Margin="0,0,10,0" Height="35" ColorType="Highlight"
-                        Text="打开 B 站" EventType="打开网页" EventData="https://www.bilibili.com/" />
-            <local:MyButton Grid.Column="1" Margin="0,0,10,0" Height="35" 
-                        Text="进入 Hypixel" EventType="启动游戏" EventData="1.16.3|mc.hypixel.net" ToolTip="使用 Minecraft 1.16.3 进入 Hypixel" />
-            <local:MyButton Grid.Column="2" Margin="0,0,10,0" Height="35" 
-                        Text="打开画图" EventType="打开文件" EventData="mspaint.exe" />
-        </Grid>
-    </StackPanel>
+		</StackPanel>
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="列表项 1:基础" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="列表项 1:基础" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
+	<StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="你也可以使用列表项 “local:MyListItem” 来替代按钮,其使用方式与按钮类似。" />
                     Text="你也可以使用列表项 “local:MyListItem” 来替代按钮,其使用方式与按钮类似。" />
-        <local:MyListItem  Margin="-5,2,-5,8"
-		            Logo="https://www.baidu.com/favicon.ico" Title="百度" Info="百度一下,你就知道!" 
+		<local:MyListItem  Margin="-5,2,-5,8"
+		            Logo="https://www.baidu.com/favicon.ico" Title="百度" Info="百度一下,你就知道!"
 		            EventType="打开网页" EventData="https://www.baidu.com/" Type="Clickable" />
 		            EventType="打开网页" EventData="https://www.baidu.com/" Type="Clickable" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="Logo 属性为列表项的图标,指定的可以是链接也可以是文件路径;Title 是大标题,Info 是可选的小标题(详细信息);Type 如果省略,该列表项则单纯只展示信息,仅当 Type 为 Clickable 才能触发点击效果。"/>
                     Text="Logo 属性为列表项的图标,指定的可以是链接也可以是文件路径;Title 是大标题,Info 是可选的小标题(详细信息);Type 如果省略,该列表项则单纯只展示信息,仅当 Type 为 Clickable 才能触发点击效果。"/>
-        <local:MyListItem Margin="-5,2,-5,8"
+		<local:MyListItem Margin="-5,2,-5,8"
                     Logo="pack://application:,,,/images/Blocks/Grass.png"
                     Logo="pack://application:,,,/images/Blocks/Grass.png"
 		            EventType="打开帮助" EventData="帮助/自定义帮助.json" Type="Clickable" />
 		            EventType="打开帮助" EventData="帮助/自定义帮助.json" Type="Clickable" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="EventType、EventData 与按钮几乎一致,唯一不同的是,列表项不支持联网加载帮助文件。" />
                     Text="EventType、EventData 与按钮几乎一致,唯一不同的是,列表项不支持联网加载帮助文件。" />
-    </StackPanel>
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="列表项 2:内置图片" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="列表项 2:内置图片" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
+	<StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="PCL 内置了一些 Minecraft 方块与物品图片,可以直接使用。"/>
                     Text="PCL 内置了一些 Minecraft 方块与物品图片,可以直接使用。"/>
-        <StackPanel Orientation="Horizontal" Margin="10,8,0,15" Height="30">
-            <Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/CommandBlock.png"/>
-            <!-- 命令方块 -->
-            <Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Cobblestone.png"/>
-            <!-- 圆石 -->
-            <Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/GoldBlock.png"/>
-            <!-- 金块 -->
-            <Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Grass.png"/>
-            <!-- 草方块 -->
-            <Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/GrassPath.png"/>
-            <!-- 草径 -->
-            <Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Anvil.png"/>
-            <!-- 铁砧 -->
-            <Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/RedstoneBlock.png"/>
-            <!-- 红石块 -->
-            <Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/RedstoneLampOn.png"/>
-            <!-- 红石灯(亮) -->
-            <Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/RedstoneLampOff.png"/>
-            <!-- 红石灯(灭) -->
-            <Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Egg.png"/>
-            <!-- 鸡蛋 -->
-            <Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Fabric.png"/>
-            <!-- 布料(Fabric 图标) -->
-        </StackPanel>
+		<StackPanel Orientation="Horizontal" Margin="10,8,0,15" Height="30">
+			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/CommandBlock.png"/>
+			<!-- 命令方块 -->
+			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Cobblestone.png"/>
+			<!-- 圆石 -->
+			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/GoldBlock.png"/>
+			<!-- 金块 -->
+			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Grass.png"/>
+			<!-- 草方块 -->
+			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/GrassPath.png"/>
+			<!-- 草径 -->
+			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Anvil.png"/>
+			<!-- 铁砧 -->
+			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/RedstoneBlock.png"/>
+			<!-- 红石块 -->
+			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/RedstoneLampOn.png"/>
+			<!-- 红石灯(亮) -->
+			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/RedstoneLampOff.png"/>
+			<!-- 红石灯(灭) -->
+			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Egg.png"/>
+			<!-- 鸡蛋 -->
+			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Fabric.png"/>
+			<!-- 布料(Fabric 图标) -->
+		</StackPanel>
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="使用时,将上方对应行的 Source 复制到 MyListItem 的 Logo 属性处即可。"/>
                     Text="使用时,将上方对应行的 Source 复制到 MyListItem 的 Logo 属性处即可。"/>
-        <local:MyListItem Margin="-5,0,-5,8"
+		<local:MyListItem Margin="-5,0,-5,8"
             Logo="pack://application:,,,/images/Blocks/CommandBlock.png"
             Logo="pack://application:,,,/images/Blocks/CommandBlock.png"
             Title="清理垃圾" Info="使用内置的命令方块图片作为 Logo" EventType="清理垃圾" Type="Clickable" />
             Title="清理垃圾" Info="使用内置的命令方块图片作为 Logo" EventType="清理垃圾" Type="Clickable" />
-    </StackPanel>
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="主题色" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="主题色" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
+	<StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,15"
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,15"
                     Text="你可以用类似 {DynamicResource ColorBrush5} 的格式使用 PCL 当前的主题颜色,修改末尾的数字编号以改变颜色浓度。" />
                     Text="你可以用类似 {DynamicResource ColorBrush5} 的格式使用 PCL 当前的主题颜色,修改末尾的数字编号以改变颜色浓度。" />
-        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="8,4">
+		<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="8,4">
             <TextBlock TextWrapping="Wrap" Text=" 浓度 1 " Margin="5,0" Background="{DynamicResource ColorBrush1}" Foreground="#FFFFFF" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 1 " Margin="5,0" Background="{DynamicResource ColorBrush1}" Foreground="#FFFFFF" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 2 " Margin="5,0" Background="{DynamicResource ColorBrush2}" Foreground="#FFFFFF" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 2 " Margin="5,0" Background="{DynamicResource ColorBrush2}" Foreground="#FFFFFF" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 3 " Margin="5,0" Background="{DynamicResource ColorBrush3}" Foreground="#FFFFFF" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 3 " Margin="5,0" Background="{DynamicResource ColorBrush3}" Foreground="#FFFFFF" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 4 " Margin="5,0" Background="{DynamicResource ColorBrush4}" Foreground="#FFFFFF" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 4 " Margin="5,0" Background="{DynamicResource ColorBrush4}" Foreground="#FFFFFF" />
-        </StackPanel>
-        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="8,4">
+		</StackPanel>
+		<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="8,4">
             <TextBlock TextWrapping="Wrap" Text=" 浓度 5 " Margin="5,0" Background="{DynamicResource ColorBrush5}" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 5 " Margin="5,0" Background="{DynamicResource ColorBrush5}" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 6 " Margin="5,0" Background="{DynamicResource ColorBrush6}" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 6 " Margin="5,0" Background="{DynamicResource ColorBrush6}" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 7 " Margin="5,0" Background="{DynamicResource ColorBrush7}" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 7 " Margin="5,0" Background="{DynamicResource ColorBrush7}" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 8 " Margin="5,0" Background="{DynamicResource ColorBrush8}" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 8 " Margin="5,0" Background="{DynamicResource ColorBrush8}" />
-        </StackPanel>
-        <TextBlock TextWrapping="Wrap" Margin="0,15,0,8" Foreground="{DynamicResource ColorBrush2}" Background="{DynamicResource ColorBrush6}"
+		</StackPanel>
+		<TextBlock Margin="0,15,0,8" Foreground="{DynamicResource ColorBrush2}" Background="{DynamicResource ColorBrush6}"
                     Text="除了字体颜色,你也可以将主题色用于背景色、边框颜色等颜色参数。例如这行字的颜色为浓度 2,背景为浓度 6。" />
                     Text="除了字体颜色,你也可以将主题色用于背景色、边框颜色等颜色参数。例如这行字的颜色为浓度 2,背景为浓度 6。" />
-    </StackPanel>
+	</StackPanel>
+</local:MyCard>
+
+<local:MyCard Title="替换标记" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+	<StackPanel Margin="25,40,23,15">
+		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+                    Text="在花括号 {} 中写入特定内容,PCL 会在加载时对其进行替换。&#xa;例如,path 会被替换为 PCL 可执行文件所在文件夹({path}),用于在 EventData 中指定特定文件,或是加载图片。" />
+		<local:MyHint Margin="0,6,0,10" Text="以下特性于 PCL 2.6.5 (299) 加入,不适用于更早版本的 PCL。" IsWarn="False" />
+		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+                    Text="你也可以使用替换标记来获取随机内容:cave 会被替换为一条随机的回声洞内容,hint 则是 “你知道吗” 的内容。" />
+		<TextBlock TextWrapping="Wrap" Margin="10,6,0,5" Foreground="{DynamicResource ColorBrush3}"
+                    Text="你知道吗:{hint}" />
+	</StackPanel>
+</local:MyCard>
+
+<local:MyCard Title="进阶:Grid 布局" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+	<StackPanel Margin="25,40,23,15">
+		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+                    Text="如果要实现更复杂的布局,则必须使用 Grid。Grid 可以让按钮们自动适应窗口宽度:随意拉伸 PCL 窗口,按钮大小会自动改变。" />
+		<TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
+                    Text="你可以上网查找 “WPF Grid” 的相关教程,这里仅给出一个例子,不作详细介绍。你可以仅在这个教学卡片的基础上稍作调整,来实现自己的按钮布局。" />
+		<Grid>
+			<Grid.ColumnDefinitions>
+				<ColumnDefinition Width="1*" />
+				<ColumnDefinition Width="1.6*" MinWidth="200" />
+				<!-- 第二个按钮的宽度是第一个按钮的 1.6 倍,且至少为 200 -->
+				<ColumnDefinition Width="150" />
+				<!-- 如果不打 *,则会占用固定的宽度,也可以写 Auto 来自适应 -->
+			</Grid.ColumnDefinitions>
+			<!-- 为按钮添加 Grid.Column 属性指定它所在的列,不要添加 Width 与 HorizontalAlignment 属性 -->
+			<local:MyButton Grid.Column="0" Margin="0,0,10,0" Height="35" ColorType="Highlight"
+                        Text="打开 B 站" EventType="打开网页" EventData="https://www.bilibili.com/" />
+			<local:MyButton Grid.Column="1" Margin="0,0,10,0" Height="35"
+                        Text="进入 Hypixel" EventType="启动游戏" EventData="1.16.3|mc.hypixel.net" ToolTip="使用 Minecraft 1.16.3 进入 Hypixel" />
+			<local:MyButton Grid.Column="2" Margin="0,0,10,0" Height="35"
+                        Text="打开画图" EventType="打开文件" EventData="mspaint.exe" />
+		</Grid>
+	</StackPanel>
+</local:MyCard>
+
+<local:MyCard Title="进阶:图标按钮" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+	<StackPanel Margin="25,40,23,15">
+		<local:MyHint Margin="0,0,0,10" Text="以下特性于 PCL 2.6.5 (299) 加入,不适用于更早版本的 PCL。" IsWarn="False" />
+		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+                    Text="MyIconTextButton 是一种带有自定义矢量图标的按钮变体,它同样支持 EventType 与 EventData 属性。&#xa;通过设置 Logo 属性来控制它的图标,设置 LogoScale 属性还可以调整图标的缩放比例。" />
+		<local:MyIconTextButton Margin="0,4,0,8" Height="35" HorizontalAlignment="Center"
+                    Text="查看 SVG Path 简介" EventType="打开网页" EventData="https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Paths"
+                    LogoScale="1.02" Logo="M1091 0H78C35 0 0 35 0 78v863c0 43 35 78 78 78H1091c43 0 78-35 78-78V78C1170 35 1134 0 1091 0z m-8 87v78H87v-78h994zM87 933V254h994v679H87v0zM724 340l-206 547a43 43 0 0 1-82-31l206-547a43 43 0 0 1 82 30zM369 545l-85 85 85 85a43 43 0 0 1-62 62l-116-116a43 43 0 0 1 0-62l116-116a43 43 0 0 1 62 61z m610-37a43 43 0 0 1 0 62l-116 116a43 43 0 0 1-62 0 43 43 0 0 1 0-62l85-85-85-85a43 43 0 0 1 62-62l116 116z" />
+		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+                    Text="Logo 属性的内容是 SVG Path 的值,你需要使用 SVG 编辑器或者从图标资源网站(如 IconFont)查找它。不要自己写,写不懂的。&#xa;此外,它还有两种 ColorType:Black(默认)和 Highlight。" />
+		<local:MyIconTextButton Margin="0,4,0,8" Height="35" HorizontalAlignment="Center"
+                    ColorType="Highlight" Text="打开 IconFont" EventType="打开网页" EventData="https://www.iconfont.cn/"
+                    LogoScale="1" Logo="M510 959c-51-31-40-86-74-120-61-63-168-66-231-138C-5 457 196 3 593 70c231 39 407 363 250 592-67 98-198 106-277 213-23 23-11 81-55 83zM176 432c-9 176 240 167 240 9 0-80-72-145-166-111-66 23-71 49-74 101z m509 129c212 59 217-307 0-231-102 36-90 206 0 231z m-213 111c33 7 44-91 9-92-19 19-64 79-9 92z m83 0H575c23-37-8-87-46-92 3 37-11 92 27 92z" />
+		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+                    Text="如果只想要图标,不要文字,还可以使用 MyIconButton。它不能设置 Text 属性,只有一个图标可以按。&#xa;它的 Theme 属性可以设置为 Color(默认)、White、Black、Red。" />
+		<local:MyIconButton Margin="0,4,0,0" Width="25" Height="25" HorizontalAlignment="Center"
+                    Theme="Black" EventType="弹出窗口" EventData="事件|MyIconButton 当然也支持 EventType 和 EventData——要不然介绍它干啥?"
+                    LogoScale="1" Logo="M149 873c47 47 101 83 162 109 63 26 130 40 199 40 69 0 136-13 199-40 61-25 115-62 162-109s83-101 109-162c26-63 40-130 40-199 0-69-13-136-40-199-25-61-62-115-109-162-46-46-101-83-162-109C648 13 580 0 511 0s-136 13-199 40c-61 25-115 62-162 109s-83 101-109 162C13 375 0 442 0 511s13 136 40 199c25 61 62 115 109 162zM97 511c0-228 185-414 414-414 228 0 414 185 414 414S740 926 511 926c-228 0-414-185-414-414z M539 244c-8-4-17-7-27-7-9 0-19 2-27 7-16 9-27 27-27 47 0 30 24 54 54 54 30 0 54-24 54-54 0-19-10-37-27-47zM566 732v-284c0-30-24-54-54-54-30 0-54 24-54 54v284c0 30 24 54 54 54 30 0 54-24 54-54z" />
+		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+                    Text="如果只想显示一个图标,不想让它是能按的按钮,可以使用 WPF 自带的 Path 等绘图控件,感兴趣的话可以自行搜索。想偷懒也可以把 MyIconButton 的 IsHitTestVisible 属性改为 False……" />
+		<Path Margin="0,4,0,0" Width="20" Height="20" HorizontalAlignment="Center"
+                    Stretch="Uniform" Fill="{DynamicResource ColorBrush1}"
+                    Data="M149 873c47 47 101 83 162 109 63 26 130 40 199 40 69 0 136-13 199-40 61-25 115-62 162-109s83-101 109-162c26-63 40-130 40-199 0-69-13-136-40-199-25-61-62-115-109-162-46-46-101-83-162-109C648 13 580 0 511 0s-136 13-199 40c-61 25-115 62-162 109s-83 101-109 162C13 375 0 442 0 511s13 136 40 199c25 61 62 115 109 162zM97 511c0-228 185-414 414-414 228 0 414 185 414 414S740 926 511 926c-228 0-414-185-414-414z M539 244c-8-4-17-7-27-7-9 0-19 2-27 7-16 9-27 27-27 47 0 30 24 54 54 54 30 0 54-24 54-54 0-19-10-37-27-47zM566 732v-284c0-30-24-54-54-54-30 0-54 24-54 54v284c0 30 24 54 54 54 30 0 54-24 54-54z" />
+	</StackPanel>
+</local:MyCard>
+
+<local:MyCard Title="进阶:联网自定义主页" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+	<StackPanel Margin="25,40,23,15">
+		<local:MyHint Margin="0,0,0,10" Text="以下特性于 PCL 2.6.5 (299) 加入,不适用于更早版本的 PCL。" IsWarn="False" />
+		<TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
+                    Text="如果你在尝试制作联网更新的自定义主页,可以查看下方的 GitHub 讨论页。&#xa;你可以根据其中的介绍为主页添加版本号检查以节省流量,也可以通过检查 Referer 和 User Agent 来确定对方的 PCL 版本。" />
+		<local:MyButton Margin="0,0,0,8" Height="35" HorizontalAlignment="Center" Padding="20,0,20,0"
+                    Text="打开 GitHub 讨论页" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2/discussions/2528" />
+	</StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <!-- 
 <!-- 
@@ -262,8 +310,10 @@
 全部通用的属性:
 全部通用的属性:
  - Width、Height:设置宽度与高度
  - Width、Height:设置宽度与高度
  - ToolTip:设置鼠标指向时的提示文本
  - ToolTip:设置鼠标指向时的提示文本
- - Background:调整背景颜色(十六进制字符串,例如 #FF010203 代表 ARGB 中的 255,1,2,3。前两位代表不透明度,FF 为全不透明,00 为全透明)
  - Margin:调整外边距(格式为 左,上,右,下。例如 Margin="0,0,0,2" 即为 2 单位的下边距)
  - Margin:调整外边距(格式为 左,上,右,下。例如 Margin="0,0,0,2" 即为 2 单位的下边距)
+ - HorizontalAlignment:设置横向对齐方式,居左:Left、居中:Center、居右:Right、拉伸(默认):Stretch
+ - VerticalAlignment:设置纵向对齐方式,居上:Top、居中:Center、居下:Bottom、拉伸(默认):Stretch
+ - IsHitTestVisible:该控件是否响应交互(点击、指向动画等),True 为是(默认),False 为否
 
 
 TextBlock(纯文本):
 TextBlock(纯文本):
  - Text:设置显示的文本(如果要使用引号等特殊字符,自行百度 XAML 转义)
  - Text:设置显示的文本(如果要使用引号等特殊字符,自行百度 XAML 转义)
@@ -271,37 +321,32 @@ TextBlock(纯文本):
  - FontSize:设置字号
  - FontSize:设置字号
  - FontWeight:设置为 Bold 时,文字加粗
  - FontWeight:设置为 Bold 时,文字加粗
  - Foreground:调整前景颜色(十六进制字符串,例如 #FF010203 代表 ARGB 中的 255,1,2,3。前两位代表不透明度,FF 为全不透明,00 为全透明)
  - Foreground:调整前景颜色(十六进制字符串,例如 #FF010203 代表 ARGB 中的 255,1,2,3。前两位代表不透明度,FF 为全不透明,00 为全透明)
- - HorizontalAlignment:设置对齐方式(Left、Center、Right)
 
 
 Image(图片):
 Image(图片):
- - Source:要显示的图片的网址
- - Foreground:调整前景颜色(十六进制字符串,例如 #FF010203 代表 ARGB 中的 255,1,2,3。前两位代表不透明度,FF 为全不透明,00 为全透明)
- - HorizontalAlignment:设置对齐方式(Left、Center、Right)
+ - Source:要显示的图片的网址,或 PCL 内置图片位置
 
 
 local:MyCard(卡片):
 local:MyCard(卡片):
  - Title:设置显示的标题文本
  - Title:设置显示的标题文本
- - CanSwap:这张卡片是否可以折叠,True 为是,False 为否
- - IsSwaped:这张卡片是否默认折叠,要求 CanSwap 必须为 True
- - Foreground:调整前景颜色(十六进制字符串,例如 #FF010203 代表 ARGB 中的 255,1,2,3。前两位代表不透明度,FF 为全不透明,00 为全透明)
- - HorizontalAlignment:设置对齐方式(Left、Center、Right)(使用时,CanSwap 属性需为 False)
+ - CanSwap:卡片是否可以折叠,True 为是,False 为否
+ - IsSwaped:卡片是否默认折叠,要求 CanSwap 必须为 True
+ - HorizontalAlignment:若使用,要求 CanSwap 必须为 False
+ - UseAnimation:是否在展开等高度改变时触发动画,True 为是(默认),False 为否
+ - SwapLogoRight:卡片折叠时的箭头是朝下还是朝右,True 为朝右,False 为朝下(默认)
+ - HasMouseAnimation:是否在鼠标指向时改变卡片颜色,True 为是(默认),False 为否
 
 
 local:MyHint(提示条):
 local:MyHint(提示条):
  - Text:设置显示的文本(&#xA; 代表换行)
  - Text:设置显示的文本(&#xA; 代表换行)
  - IsWarn:设置为 True 代表警告样式,False 代表提示样式
  - IsWarn:设置为 True 代表警告样式,False 代表提示样式
- - HorizontalAlignment:设置对齐方式(Left、Center、Right)
 
 
 local:MyButton(按钮):
 local:MyButton(按钮):
  - Text:设置显示的文本
  - Text:设置显示的文本
  - Padding:设置内边距,格式与 Margin 一致
  - Padding:设置内边距,格式与 Margin 一致
  - ColorType:设置颜色主题(Highlight 为当前启动器的主题颜色,Red 为红色,默认为黑色)
  - ColorType:设置颜色主题(Highlight 为当前启动器的主题颜色,Red 为红色,默认为黑色)
  - EventType、EventData:触发特定事件
  - EventType、EventData:触发特定事件
- - HorizontalAlignment:设置对齐方式(Left、Center、Right)
 
 
 local:MyTextButton(文本按钮):
 local:MyTextButton(文本按钮):
  - Text:设置显示的文本
  - Text:设置显示的文本
  - EventType、EventData:触发特定事件
  - EventType、EventData:触发特定事件
- - Foreground:调整前景颜色(十六进制字符串,例如 #FF010203 代表 ARGB 中的 255,1,2,3。前两位代表不透明度,FF 为全不透明,00 为全透明)
- - HorizontalAlignment:设置对齐方式(Left、Center、Right)
 
 
 local:MyListItem(列表项):
 local:MyListItem(列表项):
  - Logo:列表项的图标(所指定的可以是链接也可以是文件路径)
  - Logo:列表项的图标(所指定的可以是链接也可以是文件路径)
@@ -309,4 +354,17 @@ local:MyListItem(列表项):
  - Info:列表项的详细信息,它将会以灰色显示在大标题下方
  - Info:列表项的详细信息,它将会以灰色显示在大标题下方
  - Type:若留空仅单纯展示信息,设置为 Clickable 触发相应活动
  - Type:若留空仅单纯展示信息,设置为 Clickable 触发相应活动
 
 
--->
+local:MyIconTextButton(带图标的按钮):
+ - Text:设置显示的文本
+ - Logo:设置显示的图标
+ - LogoScale:设置图标的缩放,默认为 1
+ - ColorType:设置颜色主题(Highlight 为当前启动器的主题颜色,默认为黑色)
+ - EventType、EventData:触发特定事件
+
+local:MyIconButton(图标按钮):
+ - Logo:设置显示的图标
+ - LogoScale:设置图标的缩放,默认为 1
+ - Theme:设置颜色主题,Color(默认)为当前启动器的主题颜色,也可设置为 White、Black、Red
+ - EventType、EventData:触发特定事件
+
+-->

binární
Plain Craft Launcher 2/Resources/Help.zip