龙腾猫跃 2 年之前
父節點
當前提交
c5ae532fd5
共有 49 個文件被更改,包括 969 次插入 和 1221 次删除
  1. 3 3
      Plain Craft Launcher 2/Application.xaml.vb
  2. 140 72
      Plain Craft Launcher 2/Controls/MyImage.vb
  3. 10 17
      Plain Craft Launcher 2/Controls/MyListItem.xaml.vb
  4. 1 3
      Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb
  5. 1 3
      Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb
  6. 4 4
      Plain Craft Launcher 2/Controls/MyTextButton.vb
  7. 2 2
      Plain Craft Launcher 2/FormMain.xaml
  8. 58 226
      Plain Craft Launcher 2/FormMain.xaml.vb
  9. 4 4
      Plain Craft Launcher 2/Modules/Base/ModBase.vb
  10. 22 3
      Plain Craft Launcher 2/Modules/Base/ModNet.vb
  11. 7 7
      Plain Craft Launcher 2/Modules/Base/ModValidate.vb
  12. 13 9
      Plain Craft Launcher 2/Modules/Base/MyBitmap.vb
  13. 18 13
      Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
  14. 16 33
      Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
  15. 17 20
      Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
  16. 1 0
      Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
  17. 4 4
      Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
  18. 2 1
      Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml
  19. 2 75
      Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb
  20. 2 2
      Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml
  21. 44 98
      Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb
  22. 12 1
      Plain Craft Launcher 2/Modules/ModEvent.vb
  23. 4 4
      Plain Craft Launcher 2/Modules/ModSecret.vb
  24. 2 2
      Plain Craft Launcher 2/My Project/AssemblyInfo.vb
  25. 172 163
      Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
  26. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb
  27. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb
  28. 9 3
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb
  29. 1 1
      Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb
  30. 2 2
      Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
  31. 3 3
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb
  32. 8 0
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb
  33. 1 1
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb
  34. 10 10
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb
  35. 0 69
      Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb
  36. 1 1
      Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml.vb
  37. 4 4
      Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb
  38. 10 2
      Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb
  39. 1 1
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml.vb
  40. 45 31
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
  41. 7 33
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb
  42. 2 0
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml
  43. 10 10
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb
  44. 1 1
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb
  45. 14 7
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
  46. 6 1
      Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
  47. 155 154
      Plain Craft Launcher 2/Resources/Custom.xaml
  48. 二進制
      Plain Craft Launcher 2/Resources/Help.zip
  49. 116 116
      Plain Craft Launcher 2/Resources/ModData.txt

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

@@ -102,7 +102,7 @@ Public Class Application
             Log($"[Start] 程序版本:{VersionDisplayName} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})")
             Log($"[Start] 程序版本:{VersionDisplayName} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})")
             Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}")
             Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}")
             Log($"[Start] 程序路径:{PathWithName}")
             Log($"[Start] 程序路径:{PathWithName}")
-            Log($"[Start] 系统编码:{Encoding.Default} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")
+            Log($"[Start] 系统编码:{Encoding.Default.HeaderName} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")
             Log($"[Start] 管理员权限:{IsAdmin()}")
             Log($"[Start] 管理员权限:{IsAdmin()}")
             '检测异常环境
             '检测异常环境
             If Path.Contains(IO.Path.GetTempPath()) OrElse Path.Contains("AppData\Local\Temp\") Then
             If Path.Contains(IO.Path.GetTempPath()) OrElse Path.Contains("AppData\Local\Temp\") Then
@@ -210,8 +210,8 @@ Public Class Application
                 If AssemblyImazenWebp Is Nothing Then
                 If AssemblyImazenWebp Is Nothing Then
                     Log("[Start] 加载 DLL:Imazen.WebP")
                     Log("[Start] 加载 DLL:Imazen.WebP")
                     AssemblyImazenWebp = Assembly.Load(GetResources("Imazen_WebP"))
                     AssemblyImazenWebp = Assembly.Load(GetResources("Imazen_WebP"))
-                    SetDllDirectory(GetPureAsciiDir())
-                    File.WriteAllBytes(GetPureAsciiDir() & "\libwebp.dll", GetResources("libwebp64"))
+                    SetDllDirectory(PathPure.TrimEnd("\"))
+                    WriteFile(PathPure & "libwebp.dll", GetResources("libwebp64"))
                 End If
                 End If
                 Return AssemblyImazenWebp
                 Return AssemblyImazenWebp
             End SyncLock
             End SyncLock

+ 140 - 72
Plain Craft Launcher 2/Controls/MyImage.vb

@@ -1,7 +1,7 @@
-Public Class MyImage
+Public Class MyImage
     Inherits Image
     Inherits Image
 
 
-#Region "辅助属性注册"
+#Region "公开属性"
 
 
     ''' <summary>
     ''' <summary>
     ''' 网络图片的缓存有效期。
     ''' 网络图片的缓存有效期。
@@ -22,14 +22,6 @@ Public Class MyImage
     End Property
     End Property
     Private _EnableCache As Boolean = True
     Private _EnableCache As Boolean = True
 
 
-    '将 Source 属性映射到 XAML
-    Public Shared Shadows ReadOnly SourceProperty As DependencyProperty = DependencyProperty.Register(
-        "Source", GetType(String), GetType(MyImage), New PropertyMetadata(New PropertyChangedCallback(
-    Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString())))
-
-#End Region
-
-    Private _Source As String = ""
     ''' <summary>
     ''' <summary>
     ''' 与 Image 的 Source 类似。
     ''' 与 Image 的 Source 类似。
     ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示,图片会保存为本地缓存。
     ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示,图片会保存为本地缓存。
@@ -43,74 +35,150 @@ Public Class MyImage
             If value = "" Then value = Nothing
             If value = "" Then value = Nothing
             If _Source = value Then Exit Property
             If _Source = value Then Exit Property
             _Source = value
             _Source = value
-            Dim TempPath As String = $"{PathTemp}MyImage\{GetHash(value)}.png"
+            If Not IsInitialized Then Exit Property '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868)
+            Load()
+        End Set
+    End Property
+    Private _Source As String = ""
+    Public Shared Shadows ReadOnly SourceProperty As DependencyProperty = DependencyProperty.Register(
+        "Source", GetType(String), GetType(MyImage), New PropertyMetadata(New PropertyChangedCallback(
+    Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString())))
+
+    ''' <summary>
+    ''' 当 Source 首次下载失败时,会从该备用地址加载图片。
+    ''' </summary>
+    Public Property FallbackSource As String
+        Get
+            Return _FallbackSource
+        End Get
+        Set(value As String)
+            _FallbackSource = value
+        End Set
+    End Property
+    Private _FallbackSource As String = Nothing
+
+    ''' <summary>
+    ''' 正在下载网络图片时显示的本地图片。
+    ''' </summary>
+    Public Property LoadingSource As String
+        Get
+            Return _LoadingSource
+        End Get
+        Set(value As String)
+            _LoadingSource = value
+        End Set
+    End Property
+    Private _LoadingSource As String = "pack://application:,,,/images/Icons/NoIcon.png"
+
+#End Region
+
+    ''' <summary>
+    ''' 实际被呈现的图片地址。
+    ''' </summary>
+    Public Property ActualSource As String
+        Get
+            Return _ActualSource
+        End Get
+        Set(value As String)
+            If value = "" Then value = Nothing
+            If _ActualSource = value Then Exit Property
+            _ActualSource = value
             Try
             Try
-                '空
-                If value Is Nothing Then
-                    MyBase.Source = Nothing
-                    Exit Property
-                End If
-                '本地图片
-                If Not value.StartsWithF("http") Then
-                    MyBase.Source = New MyBitmap(value)
-                    Exit Property
-                End If
-                '从缓存加载网络图片
-                If EnableCache AndAlso File.Exists(TempPath) Then
-                    MyBase.Source = New MyBitmap(TempPath)
-                    If (Date.Now - File.GetCreationTime(TempPath)) < FileCacheExpiredTime Then
-                        Exit Property '无需刷新缓存
-                    Else
-                        File.Delete(TempPath) '需要刷新缓存
-                    End If
-                Else
-                    MyBase.Source = Nothing '清空显示
-                End If
-                '下载网络图片
-                RunInNewThread(
-                Sub()
-                    Dim Url As String = value '重新捕获变量,以检测在下载过程中 Source 被修改的情况
-                    Dim Retried As Boolean = False
-RetryStart:
-                    Dim TempDownloadingPath As String = TempPath & RandomInteger(0, 10000000)
-                    Try
-                        Log("[MyImage] 正在下载图片:" & Url)
-                        NetDownload(Url, TempDownloadingPath, True)
-                        If Url <> Source Then
-                            '若 Source 在下载时被修改,则不显示
-                            File.Delete(TempDownloadingPath)
-                        ElseIf EnableCache Then
-                            '保存缓存并显示
-                            Rename(TempDownloadingPath, TempPath)
-                            RunInUi(Sub() MyBase.Source = New MyBitmap(TempPath))
-                        Else
-                            '直接显示
-                            RunInUiWait(Sub() MyBase.Source = New MyBitmap(TempDownloadingPath))
-                            File.Delete(TempDownloadingPath)
-                        End If
-                    Catch ex As Exception
-                        Try
-                            File.Delete(TempDownloadingPath)
-                        Catch
-                        End Try
-                        If Not Retried Then
-                            Log(ex, $"下载图片可重试地失败({Url})", LogLevel.Developer)
-                            Retried = True
-                            Thread.Sleep(1000)
-                            GoTo RetryStart
-                        Else
-                            Log(ex, $"下载图片失败({Url})", LogLevel.Hint)
-                        End If
-                    End Try
-                End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal)
+                Dim Bitmap As MyBitmap = If(value Is Nothing, Nothing, New MyBitmap(value)) '在这里先触发可能的文件读取,尽量避免在 UI 线程中读取文件
+                RunInUiWait(Sub() MyBase.Source = Bitmap)
             Catch ex As Exception
             Catch ex As Exception
-                Log(ex, $"加载图片失败({value})", LogLevel.Hint)
+                Log(ex, $"加载图片失败({value})")
                 Try
                 Try
-                    File.Delete(TempPath) '删除缓存,以免缓存出现问题导致一直加载失败
+                    If value.StartsWithF(PathTemp) AndAlso File.Exists(value) Then File.Delete(value)
                 Catch
                 Catch
                 End Try
                 End Try
             End Try
             End Try
         End Set
         End Set
     End Property
     End Property
+    Private _ActualSource As String = Nothing
+
+    Private Sub Load() _
+        Handles Me.Initialized '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868)
+        '空
+        If Source Is Nothing Then
+            ActualSource = Nothing
+            Exit Sub
+        End If
+        '本地图片
+        If Not Source.StartsWithF("http") Then
+            ActualSource = Source
+            Exit Sub
+        End If
+        '从缓存加载网络图片
+        Dim Url As String = Source
+        Dim Retried As Boolean = False
+        Dim TempPath As String = GetTempPath(Url)
+        Dim TempFile As New FileInfo(TempPath)
+        If EnableCache AndAlso TempFile.Exists Then
+            ActualSource = TempPath
+            If (Date.Now - TempFile.CreationTime) < FileCacheExpiredTime Then Exit Sub '无需刷新缓存
+        End If
+        RunInNewThread(
+        Sub()
+            Dim TempDownloadingPath As String = Nothing
+            Try
+RetryStart:
+                '下载
+                ActualSource = LoadingSource '显示加载中图片
+                TempDownloadingPath = TempPath & RandomInteger(0, 10000000)
+                NetDownload(Url, TempDownloadingPath, True)
+                If Url <> Source AndAlso Url <> FallbackSource Then
+                    '已经更换了地址
+                    File.Delete(TempDownloadingPath)
+                ElseIf EnableCache Then
+                    '保存缓存并显示
+                    If File.Exists(TempPath) Then File.Delete(TempPath)
+                    Rename(TempDownloadingPath, TempPath)
+                    RunInUi(Sub() ActualSource = TempPath)
+                Else
+                    '直接显示
+                    RunInUiWait(Sub() ActualSource = TempDownloadingPath)
+                    File.Delete(TempDownloadingPath)
+                End If
+            Catch ex As Exception
+                Try
+                    If TempPath IsNot Nothing Then File.Delete(TempPath)
+                    If TempDownloadingPath IsNot Nothing Then File.Delete(TempDownloadingPath)
+                Catch
+                End Try
+                If Not Retried Then
+                    '更换备用地址
+                    Log(ex, $"下载图片可重试地失败({Url})", LogLevel.Developer)
+                    Retried = True
+                    Url = If(FallbackSource, Source)
+                    '空
+                    If Url Is Nothing Then
+                        ActualSource = Nothing
+                        Exit Sub
+                    End If
+                    '本地图片
+                    If Not Url.StartsWithF("http") Then
+                        ActualSource = Url
+                        Exit Sub
+                    End If
+                    '从缓存加载网络图片
+                    TempPath = GetTempPath(Url)
+                    TempFile = New FileInfo(TempPath)
+                    If EnableCache AndAlso TempFile.Exists() Then
+                        ActualSource = TempPath
+                        If (Date.Now - TempFile.CreationTime) < FileCacheExpiredTime Then Exit Sub '无需刷新缓存
+                    End If
+                    '下载
+                    If Source = Url Then Thread.Sleep(1000) '延迟 1s 重试
+                    GoTo RetryStart
+                Else
+                    Log(ex, $"下载图片失败({Url})", LogLevel.Hint)
+                End If
+            End Try
+        End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal)
+    End Sub
+    Public Shared Function GetTempPath(Url As String) As String
+        Return $"{PathTemp}MyImage\{GetHash(Url)}.png"
+    End Function
 
 
-End Class
+End Class

+ 10 - 17
Plain Craft Launcher 2/Controls/MyListItem.xaml.vb

@@ -212,33 +212,26 @@
             If Not _Logo = "" Then
             If Not _Logo = "" Then
                 If _Logo.StartsWithF("http", True) Then
                 If _Logo.StartsWithF("http", True) Then
                     '网络图片
                     '网络图片
-                    PathLogo = New Image With {
+                    PathLogo = New MyImage With {
                             .Tag = Me,
                             .Tag = Me,
                             .IsHitTestVisible = LogoClickable,
                             .IsHitTestVisible = LogoClickable,
-                            .Source = New ImageSourceConverter().ConvertFromString(_Logo),
+                            .Source = _Logo,
                             .RenderTransformOrigin = New Point(0.5, 0.5),
                             .RenderTransformOrigin = New Point(0.5, 0.5),
                             .RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale},
                             .RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale},
                             .SnapsToDevicePixels = True, .UseLayoutRounding = False}
                             .SnapsToDevicePixels = True, .UseLayoutRounding = False}
                     RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.Linear)
                     RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.Linear)
-                ElseIf _Logo.EndsWithF(".png", True) OrElse _Logo.EndsWithF(".jpg", True) Then
+                ElseIf _Logo.EndsWithF(".png", True) OrElse _Logo.EndsWithF(".jpg", True) OrElse _Logo.EndsWithF(".webp", True) Then
                     '位图
                     '位图
-                    Dim Bitmap = New MyBitmap(_Logo)
                     PathLogo = New Canvas With {
                     PathLogo = New Canvas With {
                             .Tag = Me,
                             .Tag = Me,
                             .IsHitTestVisible = LogoClickable,
                             .IsHitTestVisible = LogoClickable,
-                            .Background = Bitmap,
+                            .Background = New MyBitmap(_Logo),
                             .RenderTransformOrigin = New Point(0.5, 0.5),
                             .RenderTransformOrigin = New Point(0.5, 0.5),
                             .RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale},
                             .RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale},
-                            .SnapsToDevicePixels = True, .UseLayoutRounding = False}
-                    'If Bitmap.Pic.Width = 16 AndAlso Bitmap.Pic.Height = 16 Then
-                    '    '使用最适合 16x16 物品图片显示的大小
-                    '    RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.NearestNeighbor)
-                    '    PathLogo.HorizontalAlignment = HorizontalAlignment.Center : PathLogo.VerticalAlignment = VerticalAlignment.Center
-                    '    PathLogo.Width = GetWPFSize(Math.Floor(GetPixelSize(32) / 16) * 16) : PathLogo.Height = PathLogo.Width
-                    'Else
-                    PathLogo.HorizontalAlignment = HorizontalAlignment.Stretch : PathLogo.VerticalAlignment = VerticalAlignment.Stretch
-                    RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.HighQuality)
-                    'End If
+                            .SnapsToDevicePixels = True, .UseLayoutRounding = False,
+                            .HorizontalAlignment = HorizontalAlignment.Stretch, .VerticalAlignment = VerticalAlignment.Stretch
+                    }
+                    RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.Linear)
                 Else
                 Else
                     '矢量图
                     '矢量图
                     PathLogo = New Shapes.Path With {
                     PathLogo = New Shapes.Path With {
@@ -327,8 +320,8 @@
     Private Sub OnSizeChanged() Handles Me.SizeChanged
     Private Sub OnSizeChanged() Handles Me.SizeChanged
         ColumnCheck.Width = New GridLength(If(_Type = CheckType.None OrElse _Type = CheckType.Clickable, If(Height < 40, 4, 2), 6))
         ColumnCheck.Width = New GridLength(If(_Type = CheckType.None OrElse _Type = CheckType.Clickable, If(Height < 40, 4, 2), 6))
         ColumnLogo.Width = New GridLength(If(_Logo = "", 0, 34) + If(Height < 40, 0, 4))
         ColumnLogo.Width = New GridLength(If(_Logo = "", 0, 34) + If(Height < 40, 0, 4))
-        If Not IsNothing(PathLogo) Then
-            If _Logo.EndsWithF(".png", True) Then
+        If PathLogo IsNot Nothing Then
+            If _Logo.EndsWithF(".png", True) OrElse _Logo.EndsWithF(".jpg", True) OrElse _Logo.EndsWithF(".webp", True) Then
                 PathLogo.Margin = New Thickness(4, 5, 3, 5)
                 PathLogo.Margin = New Thickness(4, 5, 3, 5)
             Else
             Else
                 PathLogo.Margin = New Thickness(If(Height < 40, 6, 8), 8, If(Height < 40, 4, 6), 8)
                 PathLogo.Margin = New Thickness(If(Height < 40, 6, 8), 8, If(Height < 40, 4, 6), 8)

+ 1 - 3
Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb

@@ -157,9 +157,7 @@
         End Set
         End Set
     End Property '内容
     End Property '内容
     Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyRadioBox), New PropertyMetadata(New PropertyChangedCallback(
     Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyRadioBox), New PropertyMetadata(New PropertyChangedCallback(
-                                                                                                                                                               Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
-                                                                                                                                                                   If Not IsNothing(sender) Then CType(sender, MyRadioBox).LabText.Text = e.NewValue
-                                                                                                                                                               End Sub)))
+    Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyRadioBox).LabText.Text = e.NewValue)))
 
 
     '点击事件
     '点击事件
 
 

+ 1 - 3
Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb

@@ -117,9 +117,7 @@
         End Set
         End Set
     End Property '内容
     End Property '内容
     Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyRadioButton), New PropertyMetadata(New PropertyChangedCallback(
     Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyRadioButton), New PropertyMetadata(New PropertyChangedCallback(
-                                                                                                                                                               Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
-                                                                                                                                                                   If Not IsNothing(sender) Then CType(sender, MyRadioButton).LabText.Text = e.NewValue
-                                                                                                                                                               End Sub)))
+    Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyRadioButton).LabText.Text = e.NewValue)))
     Public Enum ColorState
     Public Enum ColorState
         White
         White
         Highlight
         Highlight

+ 4 - 4
Plain Craft Launcher 2/Controls/MyTextButton.vb

@@ -90,21 +90,21 @@
         Get
         Get
             Return GetValue(EventTypeProperty)
             Return GetValue(EventTypeProperty)
         End Get
         End Get
-
         Set(value As String)
         Set(value As String)
             SetValue(EventTypeProperty, value)
             SetValue(EventTypeProperty, value)
         End Set
         End Set
     End Property
     End Property
-    Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
+    Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register(
+        "EventType", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
     Public Property EventData As String
     Public Property EventData As String
         Get
         Get
             Return GetValue(EventDataProperty)
             Return GetValue(EventDataProperty)
         End Get
         End Get
-
         Set(value As String)
         Set(value As String)
             SetValue(EventDataProperty, value)
             SetValue(EventDataProperty, value)
         End Set
         End Set
     End Property
     End Property
-    Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
+    Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register(
+        "EventData", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
 
 
 End Class
 End Class

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

@@ -104,7 +104,7 @@
                             <GradientStop Color="{StaticResource ColorObject3}" Offset="1"/>
                             <GradientStop Color="{StaticResource ColorObject3}" Offset="1"/>
                         </LinearGradientBrush>
                         </LinearGradientBrush>
                     </Grid.Background>
                     </Grid.Background>
-                    <Image x:Name="ImgTitle" IsHitTestVisible="False" Stretch="UniformToFill" IsEnabled="False" RenderOptions.BitmapScalingMode="LowQuality" />
+                    <local:MyImage x:Name="ImgTitle" IsHitTestVisible="False" Stretch="UniformToFill" IsEnabled="False" RenderOptions.BitmapScalingMode="LowQuality" />
                     <local:MyIconButton x:Name="BtnTitleClose" HorizontalAlignment="Right" Height="28" VerticalAlignment="Center" Width="28" Margin="0,0,12,0" Theme="White" 
                     <local:MyIconButton x:Name="BtnTitleClose" HorizontalAlignment="Right" Height="28" VerticalAlignment="Center" Width="28" Margin="0,0,12,0" Theme="White" 
                                        LogoScale="0.72" Logo="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
                                        LogoScale="0.72" Logo="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
                     <local:MyIconButton x:Name="BtnTitleMin" HorizontalAlignment="Right" Height="28" VerticalAlignment="Center" Width="28" Margin="0,0,44,0" Theme="White" 
                     <local:MyIconButton x:Name="BtnTitleMin" HorizontalAlignment="Right" Height="28" VerticalAlignment="Center" Width="28" Margin="0,0,44,0" Theme="White" 
@@ -117,7 +117,7 @@
                         </Grid.ColumnDefinitions>
                         </Grid.ColumnDefinitions>
                         <Path x:Name="ShapeTitleLogo" Data="M26,29 v-25 h5 a7,7 180 0 1 0,14 h-5 M61,6.5 a10,11.5 180 1 0 0,18 M68,2.5 v24.5 h12 M98,2 v27 M107,2 v27" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="19,15.5,0,15.5" Stretch="fill" Stroke="White" StrokeThickness="1.9" Width="53" />
                         <Path x:Name="ShapeTitleLogo" Data="M26,29 v-25 h5 a7,7 180 0 1 0,14 h-5 M61,6.5 a10,11.5 180 1 0 0,18 M68,2.5 v24.5 h12 M98,2 v27 M107,2 v27" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="19,15.5,0,15.5" Stretch="fill" Stroke="White" StrokeThickness="1.9" Width="53" />
                         <TextBlock x:Name="LabTitleLogo" Visibility="Collapsed" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="18,0,0,0" Foreground="White" FontSize="17" VerticalAlignment="Center" />
                         <TextBlock x:Name="LabTitleLogo" Visibility="Collapsed" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="18,0,0,0" Foreground="White" FontSize="17" VerticalAlignment="Center" />
-                        <Image x:Name="ImageTitleLogo" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="7" VerticalAlignment="Center" Visibility="Collapsed" />
+                        <local:MyImage x:Name="ImageTitleLogo" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="7" VerticalAlignment="Center" Visibility="Collapsed" />
                         <StackPanel Grid.Column="1" x:Name="PanTitleSelect" HorizontalAlignment="Left" Height="27" VerticalAlignment="Center" Orientation="Horizontal" Margin="13,0">
                         <StackPanel Grid.Column="1" x:Name="PanTitleSelect" HorizontalAlignment="Left" Height="27" VerticalAlignment="Center" Orientation="Horizontal" Margin="13,0">
                             <local:MyRadioButton x:Name="BtnTitleSelect0" Text="{DynamicResource LangTitleHome}" Tag="0" Margin="5,0" Checked="True" Padding="2,0"
                             <local:MyRadioButton x:Name="BtnTitleSelect0" Text="{DynamicResource LangTitleHome}" Tag="0" Margin="5,0" Checked="True" Padding="2,0"
                                              LogoScale="0.95" Logo="M985.403 552.96C985.403 812.702 773.041 1024 512 1024S38.597 812.702 38.597 552.96c0-158.208 79.163-304.955 211.771-392.625 31.074-20.52 72.94-11.973 93.46 19.101s12.012 72.94-19.062 93.46c-94.681 62.622-151.237 167.306-151.237 280.064 0 185.344 151.788 336.108 338.471 336.108S850.471 738.304 850.471 552.96c0-114.649-57.974-220.278-155.096-282.585-31.35-20.125-40.448-61.833-20.362-93.184s61.794-40.448 93.184-20.4c135.995 87.236 217.206 235.362 217.206 396.169zM512 560.128c37.258 0 67.466-30.208 67.466-67.466V67.466C579.466 30.208 549.258 0 512 0s-67.466 30.208-67.466 67.466v425.196c0 37.258 30.208 67.466 67.466 67.466z" />
                                              LogoScale="0.95" Logo="M985.403 552.96C985.403 812.702 773.041 1024 512 1024S38.597 812.702 38.597 552.96c0-158.208 79.163-304.955 211.771-392.625 31.074-20.52 72.94-11.973 93.46 19.101s12.012 72.94-19.062 93.46c-94.681 62.622-151.237 167.306-151.237 280.064 0 185.344 151.788 336.108 338.471 336.108S850.471 738.304 850.471 552.96c0-114.649-57.974-220.278-155.096-282.585-31.35-20.125-40.448-61.833-20.362-93.184s61.794-40.448 93.184-20.4c135.995 87.236 217.206 235.362 217.206 396.169zM512 560.128c37.258 0 67.466-30.208 67.466-67.466V67.466C579.466 30.208 549.258 0 512 0s-67.466 30.208-67.466 67.466v425.196c0 37.258 30.208 67.466 67.466 67.466z" />

+ 58 - 226
Plain Craft Launcher 2/FormMain.xaml.vb

@@ -69,84 +69,19 @@ Public Class FormMain
             FeatureCount += 10
             FeatureCount += 10
             BugCount += 10
             BugCount += 10
         End If
         End If
-        If LastVersion < 317 Then 'Release 2.6.15
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法安装 Forge 的 Bug"))
-            FeatureCount += 2
-            BugCount += 2
-        End If
-        If LastVersion < 315 Then 'Release 2.6.14
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化 MC 下载,尝试解决各种导致 MC 下载失败的问题"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "由于 MCBBS 关站,移除其相关内容"))
-            FeatureCount += 17
-            BugCount += 16
-        End If
-        If LastVersion < 313 Then 'Release 2.6.13
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法启动 Forge 1.18.3+ 的 Bug"))
-            FeatureCount += 6
-            BugCount += 10
-        End If
-        If LastVersion < 311 Then 'Release 2.6.12
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "Mod 管理页面将显示 Mod 的中文名、图标、标签等信息"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "从 Mod 管理页面查看 Mod 信息时会跳转到其下载详情页面"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "重新设计 Mod 管理页面的交互与样式"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法安装 Forge 1.18.3+ 的 Bug"))
-            FeatureCount += 27
-            BugCount += 25
-        End If
-        If LastVersion < 308 Then 'Release 2.6.10
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "为版本独立设置添加忽略 Java 兼容性警告选项"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复开始或结束游戏时可能报错的 Bug"))
-            FeatureCount += 31
-            BugCount += 37
-        End If
-        If LastVersion < 302 Then 'Release 2.6.7
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "再次修复网络条件差时可能无法下载 MC 的 Bug"))
-            BugCount += 1
-        End If
-        If LastVersion < 300 Then 'Release 2.6.6
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持选择多种预设的主页"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "Mod 管理中允许多选 Mod 进行批量操作"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化崩溃分析,添加多种崩溃情况的判断"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化联网获取的主页的加载与缓存"))
-            FeatureCount += 40
-            BugCount += 34
-        End If
-        If LastVersion < 296 Then 'Release 2.6.3
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增内存优化功能,可以将所有程序的物理内存占用降低约 1/3"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "在选择 OptiFine 与 Fabric 后会自动选择 OptiFabric"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化崩溃分析,添加多种崩溃情况的判断"))
-            FeatureCount += 21
-            BugCount += 30
-        End If
-        If LastVersion < 293 Then 'Release 2.6.1
-            BugCount += 1
-        End If
-        If LastVersion < 292 Then 'Release 2.6.0
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "支持在多个正版账号间切换"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "在缺少 Java 时会自动下载所需的 Java"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "重新制作正版登录页面"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "彻底移除 Mojang 登录"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "添加 CurseForge / Modrinth 来源筛选"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "Mod / 整合包下载会单独列出筛选的版本"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "为下载管理和音乐播放按钮添加进度条"))
-            FeatureCount += 24
-            BugCount += 24
-        End If
-        If LastVersion < 286 Then 'Release 2.5.2
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "支持搜索、安装 Modrinth 的 Mod 与整合包"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "资源搜索页面支持翻页"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "在全局启动设置中添加了 启动前执行命令 选项"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "重做资源下载的资源项界面"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "在选择下载 Fabric 时会自动选择 Fabric API"))
-            FeatureCount += 35
-            BugCount += 36
-        End If
 #Else
 #Else
         '5:          FEAT+
         '5:          FEAT+
         '4:     IMP+ FEAT*
         '4:     IMP+ FEAT*
         '3:BUG+ IMP* FEAT-
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '2:BUG* IMP-
         '1:BUG-
         '1:BUG-
+        If LastVersion < 341 Then 'Snapshot 2.8.9
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "支持下载原版服务端"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "本地 Mod 的标题支持选择显示 Mod 原始文件名"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复搜索后启用/禁用 Mod 时出错的 Bug"))
+            FeatureCount += 17
+            BugCount += 13
+        End If
         If LastVersion < 339 Then 'Snapshot 2.8.8
         If LastVersion < 339 Then 'Snapshot 2.8.8
             If LastVersion = 337 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复数个与新正版登录相关的严重 Bug"))
             If LastVersion = 337 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复数个与新正版登录相关的严重 Bug"))
             FeatureCount += 3
             FeatureCount += 3
@@ -233,117 +168,6 @@ Public Class FormMain
             FeatureCount += 10
             FeatureCount += 10
             BugCount += 10
             BugCount += 10
         End If
         End If
-        If LastVersion < 316 Then 'Snapshot 2.6.15
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法安装 Forge 的 Bug"))
-            FeatureCount += 2
-            BugCount += 2
-        End If
-        If LastVersion < 314 Then 'Snapshot 2.6.14
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化 MC 下载,尝试解决各种导致 MC 下载失败的问题"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "由于 MCBBS 关站,移除其相关内容"))
-            FeatureCount += 17
-            BugCount += 16
-        End If
-        If LastVersion < 312 Then 'Snapshot 2.6.13
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法启动 Forge 1.18.3+ 的 Bug"))
-            FeatureCount += 6
-            BugCount += 10
-        End If
-        If LastVersion < 310 Then 'Snapshot 2.6.12
-            If LastVersion = 309 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "Mod 管理页面添加启用、禁用单个 Mod 的快捷按钮"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法安装 Forge 1.18.3+ 的 Bug"))
-            FeatureCount += 13
-            BugCount += 12
-        End If
-        If LastVersion < 309 Then 'Snapshot 2.6.11
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "Mod 管理页面将显示 Mod 的中文名、图标、标签等信息"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "从 Mod 管理页面查看 Mod 信息时会跳转到其下载详情页面"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "重新设计 Mod 管理页面的交互与样式"))
-            FeatureCount += 15
-            BugCount += 13
-        End If
-        If LastVersion < 307 Then 'Snapshot 2.6.10
-            FeatureCount += 2
-            BugCount += 3
-        End If
-        If LastVersion < 306 Then 'Snapshot 2.6.9
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "为版本独立设置添加忽略 Java 兼容性警告选项"))
-            FeatureCount += 9
-            BugCount += 11
-        End If
-        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
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "再次修复网络条件差时可能无法下载 MC 的 Bug"))
-            BugCount += 1
-        End If
-        If LastVersion < 301 Then 'Snapshot 2.6.6
-            FeatureCount += 4
-            BugCount += 2
-        End If
-        If LastVersion < 299 Then 'Snapshot 2.6.5
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持选择多种预设的主页"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化联网获取的主页的加载与缓存"))
-            FeatureCount += 18
-            BugCount += 13
-        End If
-        If LastVersion < 298 Then 'Snapshot 2.6.4
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "Mod 管理中允许多选 Mod 进行批量操作"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化崩溃分析,添加多种崩溃情况的判断"))
-            FeatureCount += 18
-            BugCount += 19
-        End If
-        If LastVersion < 297 Then 'Snapshot 2.6.3
-            FeatureCount += 12
-            BugCount += 14
-        End If
-        If LastVersion < 294 Then 'Snapshot 2.6.2
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增内存优化功能,可以将所有程序的物理内存占用降低约 1/3"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "在选择 OptiFine 与 Fabric 后会自动选择 OptiFabric"))
-            FeatureCount += 9
-            BugCount += 16
-        End If
-        If LastVersion < 291 Then 'Snapshot 2.6.0
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "支持在多个正版账号间切换"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "Mod / 整合包下载会单独列出筛选的版本"))
-            FeatureCount += 10
-            BugCount += 2
-        End If
-        If LastVersion < 289 Then 'Snapshot 2.5.4
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "重新制作正版登录页面"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "彻底移除 Mojang 登录"))
-            FeatureCount += 6
-            BugCount += 14
-        End If
-        If LastVersion < 288 Then 'Snapshot 2.5.3
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "在缺少 Java 时会自动下载所需的 Java"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "添加 CurseForge / Modrinth 来源筛选"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "为下载管理和音乐播放按钮添加进度条"))
-            FeatureCount += 8
-            BugCount += 8
-        End If
-        If LastVersion < 285 Then 'Snapshot 2.5.2
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "重做资源下载的资源项界面"))
-            If LastVersion = 284 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复打开部分帮助报错的 Bug"))
-            FeatureCount += 8
-            BugCount += 8
-        End If
-        If LastVersion < 284 Then 'Snapshot 2.5.1
-            If LastVersion >= 275 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "在全局启动设置中添加了 启动前执行命令 选项"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "在选择下载 Fabric 时会自动选择 Fabric API"))
-            FeatureCount += 22
-            BugCount += 21
-        End If
-        If LastVersion < 283 Then 'Snapshot 2.5.0
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "支持搜索、下载 Modrinth 中的 Mod 与整合包"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "资源搜索页面支持翻页"))
-            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持安装 Modrinth 整合包"))
-            FeatureCount += 5
-            BugCount += 5
-        End If
 #End If
 #End If
         '整理更新日志文本
         '整理更新日志文本
         Dim ContentList As New List(Of String)
         Dim ContentList As New List(Of String)
@@ -386,9 +210,6 @@ Public Class FormMain
             '触发降级
             '触发降级
             DowngradeSub(LastVersion)
             DowngradeSub(LastVersion)
         End If
         End If
-        ''刷新语言
-        'Lang = ReadReg("Lang", "zh_CN")
-        'Application.Current.Resources.MergedDictionaries(1) = New ResourceDictionary With {.Source = New Uri("Resources\Language\" & Lang & ".xaml", UriKind.Relative)}
         '刷新主题
         '刷新主题
         ThemeCheckAll(False)
         ThemeCheckAll(False)
         Setup.Load("UiLauncherTheme")
         Setup.Load("UiLauncherTheme")
@@ -451,12 +272,12 @@ Public Class FormMain
         '加载窗口
         '加载窗口
         ThemeRefreshMain()
         ThemeRefreshMain()
         Try
         Try
-            Height = ReadReg("WindowHeight", MinHeight + 50)
-            Width = ReadReg("WindowWidth", MinWidth + 50)
+            Height = Setup.Get("WindowHeight")
+            Width = Setup.Get("WindowWidth")
         Catch ex As Exception '修复 #2019
         Catch ex As Exception '修复 #2019
             Log(ex, "读取窗口默认大小失败", LogLevel.Hint)
             Log(ex, "读取窗口默认大小失败", LogLevel.Hint)
-            Height = MinHeight + 50
-            Width = MinWidth + 50
+            Height = MinHeight + 100
+            Width = MinWidth + 100
         End Try
         End Try
 #If DEBUG Then
 #If DEBUG Then
         MinHeight = 50
         MinHeight = 50
@@ -595,6 +416,16 @@ Public Class FormMain
             Setup.Set("UiHiddenOtherHelp", False)
             Setup.Set("UiHiddenOtherHelp", False)
             Log("[Start] 已解除帮助页面的隐藏")
             Log("[Start] 已解除帮助页面的隐藏")
         End If
         End If
+        '单向迁移微软登录结果(#4836)
+        If Not Setup.Get("CacheMsV2Migrated") Then
+            Setup.Set("CacheMsV2Migrated", True)
+            Setup.Set("CacheMsV2OAuthRefresh", Setup.Get("CacheMsOAuthRefresh"))
+            Setup.Set("CacheMsV2Access", Setup.Get("CacheMsAccess"))
+            Setup.Set("CacheMsV2ProfileJson", Setup.Get("CacheMsProfileJson"))
+            Setup.Set("CacheMsV2Uuid", Setup.Get("CacheMsUuid"))
+            Setup.Set("CacheMsV2Name", Setup.Get("CacheMsName"))
+            Log("[Start] 已从老版本迁移微软登录结果")
+        End If
         '输出更新日志
         '输出更新日志
         If LastVersionCode = 0 Then Exit Sub
         If LastVersionCode = 0 Then Exit Sub
         If LowerVersionCode >= VersionCode Then Exit Sub
         If LowerVersionCode >= VersionCode Then Exit Sub
@@ -636,35 +467,35 @@ Public Class FormMain
         End If
         End If
         '关闭
         '关闭
         RunInUiWait(
         RunInUiWait(
-            Sub()
-                IsHitTestVisible = False
-                If PanBack.RenderTransform Is Nothing Then
-                    Dim TransformPos As New TranslateTransform(0, 0)
-                    Dim TransformRotate As New RotateTransform(0)
-                    Dim TransformScale As New ScaleTransform(1, 1)
-                    PanBack.RenderTransform = New TransformGroup() With {.Children = New TransformCollection({TransformRotate, TransformPos, TransformScale})}
-                    AniStart({
-                        AaOpacity(Me, -Opacity, 140, 40, New AniEaseOutFluent(AniEasePower.Weak)),
-                        AaDouble(
-                        Sub(i)
-                            TransformScale.ScaleX += i
-                            TransformScale.ScaleY += i
-                        End Sub, 0.88 - TransformScale.ScaleX, 180),
-                        AaDouble(Sub(i) TransformPos.Y += i, 20 - TransformPos.Y, 180, 0, New AniEaseOutFluent(AniEasePower.Weak)),
-                        AaDouble(Sub(i) TransformRotate.Angle += i, 0.6 - TransformRotate.Angle, 180, 0, New AniEaseInoutFluent(AniEasePower.Weak)),
-                        AaCode(
-                        Sub()
-                            IsHitTestVisible = False
-                            Top = -10000
-                            ShowInTaskbar = False
-                        End Sub, 210),
-                        AaCode(AddressOf EndProgramForce, 230)
-                    }, "Form Close")
-                Else
-                    EndProgramForce()
-                End If
-                Log("[System] 收到关闭指令")
-            End Sub)
+        Sub()
+            IsHitTestVisible = False
+            If PanBack.RenderTransform Is Nothing Then
+                Dim TransformPos As New TranslateTransform(0, 0)
+                Dim TransformRotate As New RotateTransform(0)
+                Dim TransformScale As New ScaleTransform(1, 1)
+                PanBack.RenderTransform = New TransformGroup() With {.Children = New TransformCollection({TransformRotate, TransformPos, TransformScale})}
+                AniStart({
+                    AaOpacity(Me, -Opacity, 140, 40, New AniEaseOutFluent(AniEasePower.Weak)),
+                    AaDouble(
+                    Sub(i)
+                        TransformScale.ScaleX += i
+                        TransformScale.ScaleY += i
+                    End Sub, 0.88 - TransformScale.ScaleX, 180),
+                    AaDouble(Sub(i) TransformPos.Y += i, 20 - TransformPos.Y, 180, 0, New AniEaseOutFluent(AniEasePower.Weak)),
+                    AaDouble(Sub(i) TransformRotate.Angle += i, 0.6 - TransformRotate.Angle, 180, 0, New AniEaseInoutFluent(AniEasePower.Weak)),
+                    AaCode(
+                    Sub()
+                        IsHitTestVisible = False
+                        Top = -10000
+                        ShowInTaskbar = False
+                    End Sub, 210),
+                    AaCode(AddressOf EndProgramForce, 230)
+                }, "Form Close")
+            Else
+                EndProgramForce()
+            End If
+            Log("[System] 收到关闭指令")
+        End Sub)
     End Sub
     End Sub
     Private Shared IsLogShown As Boolean = False
     Private Shared IsLogShown As Boolean = False
     Public Shared Sub EndProgramForce(Optional ReturnCode As Result = Result.Success)
     Public Shared Sub EndProgramForce(Optional ReturnCode As Result = Result.Success)
@@ -709,8 +540,8 @@ Public Class FormMain
     Public IsSizeSaveable As Boolean = False
     Public IsSizeSaveable As Boolean = False
     Private Sub FormMain_SizeChanged() Handles Me.SizeChanged, Me.Loaded
     Private Sub FormMain_SizeChanged() Handles Me.SizeChanged, Me.Loaded
         If IsSizeSaveable Then
         If IsSizeSaveable Then
-            WriteReg("WindowHeight", Height)
-            WriteReg("WindowWidth", Width)
+            Setup.Set("WindowHeight", Height)
+            Setup.Set("WindowWidth", Width)
         End If
         End If
         RectForm.Rect = New Rect(0, 0, BorderForm.ActualWidth, BorderForm.ActualHeight)
         RectForm.Rect = New Rect(0, 0, BorderForm.ActualWidth, BorderForm.ActualHeight)
         PanForm.Width = BorderForm.ActualWidth + 0.001
         PanForm.Width = BorderForm.ActualWidth + 0.001
@@ -921,11 +752,12 @@ Public Class FormMain
                     End If
                     End If
                 End If
                 End If
                 CopyFile(FilePath, Path & "PCL\Custom.xaml")
                 CopyFile(FilePath, Path & "PCL\Custom.xaml")
-                RunInUi(Sub()
-                            Setup.Set("UiCustomType", 1)
-                            FrmLaunchRight.ForceRefresh()
-                            Hint("已加载主页自定义文件!", HintType.Finish)
-                        End Sub)
+                RunInUi(
+                Sub()
+                    Setup.Set("UiCustomType", 1)
+                    FrmLaunchRight.ForceRefresh()
+                    Hint("已加载主页自定义文件!", HintType.Finish)
+                End Sub)
                 Exit Sub
                 Exit Sub
             End If
             End If
             'Mod 安装
             'Mod 安装

文件差異過大導致無法顯示
+ 4 - 4
Plain Craft Launcher 2/Modules/Base/ModBase.vb


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

@@ -939,7 +939,6 @@ RequestFinished:
                 Dim StartPosition As Long, StartSource As NetSource = Nothing
                 Dim StartPosition As Long, StartSource As NetSource = Nothing
                 Dim Th As Thread, ThreadInfo As NetThread
                 Dim Th As Thread, ThreadInfo As NetThread
                 SyncLock LockChain
                 SyncLock LockChain
-
                     '获取线程起点与下载源
                     '获取线程起点与下载源
                     '不分割
                     '不分割
                     If IsNoSplit Then GoTo Capture
                     If IsNoSplit Then GoTo Capture
@@ -1049,6 +1048,15 @@ StartThread:
                     If ModeDebug AndAlso HttpResponse.ResponseUri.OriginalString <> Info.Source.Url Then
                     If ModeDebug AndAlso HttpResponse.ResponseUri.OriginalString <> Info.Source.Url Then
                         Log($"[Download] {LocalName} {Info.Uuid}#:重定向至 {HttpResponse.ResponseUri.OriginalString}")
                         Log($"[Download] {LocalName} {Info.Uuid}#:重定向至 {HttpResponse.ResponseUri.OriginalString}")
                     End If
                     End If
+                    ''从响应头获取文件名
+                    'If Info.IsFirstThread Then
+                    '    Dim FileName As String = GetFileNameFromResponse(HttpResponse)
+                    '    If ModeDebug Then Log($"[Download] {LocalName} {Info.Uuid}#:远程文件名:{If(FileName, "未提供")}")
+                    '    If FileName IsNot Nothing AndAlso LocalName = "待定" Then
+                    '        LocalName = FileName
+                    '        Log($"[Download] {LocalName} {Info.Uuid}#:从响应头获取到文件名")
+                    '    End If
+                    'End If
                     '文件大小校验
                     '文件大小校验
                     ContentLength = HttpResponse.ContentLength
                     ContentLength = HttpResponse.ContentLength
                     If ContentLength = -1 Then
                     If ContentLength = -1 Then
@@ -1133,7 +1141,7 @@ NotSupportRange:
                                 If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount
                                 If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount
                             End SyncLock
                             End SyncLock
                             Dim DeltaTime = GetTimeTick() - Info.LastReceiveTime
                             Dim DeltaTime = GetTimeTick() - Info.LastReceiveTime
-                            If DeltaTime > 1000000 Then DeltaTime = 0 '时间刻反转导致出现极大值
+                            If DeltaTime > 1000000 Then DeltaTime = 1 '时间刻反转导致出现极大值
                             If RealDataCount > 0 Then
                             If RealDataCount > 0 Then
                                 '有数据
                                 '有数据
                                 If Info.DownloadDone = 0 Then
                                 If Info.DownloadDone = 0 Then
@@ -1174,7 +1182,7 @@ NotSupportRange:
                                     ResultStream.Write(HttpData, 0, RealDataCount)
                                     ResultStream.Write(HttpData, 0, RealDataCount)
                                 End If
                                 End If
                                 '检查速度是否过慢
                                 '检查速度是否过慢
-                                If DeltaTime > 1000 AndAlso DeltaTime > RealDataCount Then '数据包间隔大于 1s,且速度小于 1K/s
+                                If DeltaTime > 1500 AndAlso DeltaTime > RealDataCount Then '数据包间隔大于 1.5s,且速度小于 1.5K/s
                                     Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。")
                                     Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。")
                                 End If
                                 End If
                                 Info.LastReceiveTime = GetTimeTick()
                                 Info.LastReceiveTime = GetTimeTick()
@@ -1286,6 +1294,17 @@ Wrong:
                 If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso State < NetState.Merge Then Merge()
                 If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso State < NetState.Merge Then Merge()
             End Try
             End Try
         End Sub
         End Sub
+        ''' <summary>
+        ''' 从 HTTP 响应头中获取文件名。
+        ''' 如果没有,返回 Nothing。
+        ''' </summary>
+        Private Function GetFileNameFromResponse(response As HttpWebResponse) As String
+            Dim header As String = response.Headers("Content-Disposition")
+            If String.IsNullOrEmpty(header) Then Return Nothing
+            'attachment; filename="filename.ext"
+            If Not header.Contains("filename=") Then Return Nothing
+            Return header.After("filename=").Trim(""""c, " "c).Before(";")
+        End Function
 
 
         '下载文件的最终收束事件
         '下载文件的最终收束事件
         ''' <summary>
         ''' <summary>

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

@@ -233,8 +233,8 @@ Public Class ValidateFolderName
             '检查特殊字符串
             '检查特殊字符串
             Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!", True).Validate(Str)
             Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!", True).Validate(Str)
             If Not InvalidStrCheck = "" Then Return InvalidStrCheck
             If Not InvalidStrCheck = "" Then Return InvalidStrCheck
-            '检查 NTFS 8.3 (issue #4505)
-            If Ntfs83NameCheck(Str) Then Return "文件夹名不能是一个有效的 NTFS 8.3 文件名!"
+            '检查 NTFS 8.3 文件名(#4505)
+            If RegexCheck(Str, ".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
             '检查文件夹重名
             '检查文件夹重名
             Dim Arr As New List(Of String)
             Dim Arr As New List(Of String)
             If PathIgnore IsNot Nothing Then
             If PathIgnore IsNot Nothing Then
@@ -288,8 +288,8 @@ Public Class ValidateFileName
             '检查特殊字符串
             '检查特殊字符串
             Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件名不可为 %!", True).Validate(Str)
             Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件名不可为 %!", True).Validate(Str)
             If Not InvalidStrCheck = "" Then Return InvalidStrCheck
             If Not InvalidStrCheck = "" Then Return InvalidStrCheck
-            '检查 NTFS 8.3 (issue #4505)
-            If Ntfs83NameCheck(Str) Then Return "文件名不能是一个有效的 NTFS 8.3 文件名!"
+            '检查 NTFS 8.3 文件名(#4505)
+            If RegexCheck(Str, ".{2,}~\d") Then Return "文件名不能包含这一特殊格式!"
             '检查文件重名
             '检查文件重名
             If ParentFolder IsNot Nothing Then
             If ParentFolder IsNot Nothing Then
                 Dim DirInfo As New DirectoryInfo(ParentFolder)
                 Dim DirInfo As New DirectoryInfo(ParentFolder)
@@ -355,9 +355,9 @@ Fin:
             '检查特殊字符串
             '检查特殊字符串
             Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!").Validate(SubStr)
             Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!").Validate(SubStr)
             If Not InvalidStrCheck = "" Then Return InvalidStrCheck
             If Not InvalidStrCheck = "" Then Return InvalidStrCheck
-            '检查 NTFS 8.3 (issue #4505)
-            If Ntfs83NameCheck(Str) Then Return "文件夹名不能是一个有效的 NTFS 8.3 文件名!"
+            '检查 NTFS 8.3 文件名(#4505)
+            If RegexCheck(Str, ".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
         Next
         Next
         Return ""
         Return ""
     End Function
     End Function
-End Class
+End Class

+ 13 - 9
Plain Craft Launcher 2/Modules/Base/MyBitmap.vb

@@ -7,7 +7,7 @@ Public Class MyBitmap
     ''' <summary>
     ''' <summary>
     ''' 位图缓存。
     ''' 位图缓存。
     ''' </summary>
     ''' </summary>
-    Public Shared BitmapCache As New Dictionary(Of String, MyBitmap)
+    Public Shared BitmapCache As New Concurrent.ConcurrentDictionary(Of String, MyBitmap)
 
 
     ''' <summary>
     ''' <summary>
     ''' 存储的图片
     ''' 存储的图片
@@ -61,7 +61,7 @@ Public Class MyBitmap
                     Pic = BitmapCache(FilePathOrResourceName).Pic
                     Pic = BitmapCache(FilePathOrResourceName).Pic
                 Else
                 Else
                     Pic = New MyBitmap(CType((New ImageSourceConverter).ConvertFromString(FilePathOrResourceName), ImageSource))
                     Pic = New MyBitmap(CType((New ImageSourceConverter).ConvertFromString(FilePathOrResourceName), ImageSource))
-                    BitmapCache.Add(FilePathOrResourceName, Pic)
+                    BitmapCache.TryAdd(FilePathOrResourceName, Pic)
                 End If
                 End If
             Else
             Else
                 '使用这种自己接管 FileStream 的方法加载才能解除文件占用
                 '使用这种自己接管 FileStream 的方法加载才能解除文件占用
@@ -72,11 +72,9 @@ Public Class MyBitmap
                     InputStream.Seek(0, SeekOrigin.Begin)
                     InputStream.Seek(0, SeekOrigin.Begin)
                     If Header(0) = 82 AndAlso Header(1) = 73 Then
                     If Header(0) = 82 AndAlso Header(1) = 73 Then
                         '读取 WebP
                         '读取 WebP
-                        If Is32BitSystem Then Throw New Exception("不支持在 32 位系统下加载 WebP 图片。")
                         Dim FileBytes(InputStream.Length - 1) As Byte
                         Dim FileBytes(InputStream.Length - 1) As Byte
                         InputStream.Read(FileBytes, 0, FileBytes.Length)
                         InputStream.Read(FileBytes, 0, FileBytes.Length)
-                        Dim Decoder As New Imazen.WebP.SimpleDecoder()
-                        Pic = Decoder.DecodeFromBytes(FileBytes, FileBytes.Length)
+                        Pic = WebPDecoder.DecodeFromBytes(FileBytes) '将代码隔离在另外一个类中,这样只要不走进这个分支就不会加载 Imazen.WebP.dll
                     Else
                     Else
                         Pic = New System.Drawing.Bitmap(InputStream)
                         Pic = New System.Drawing.Bitmap(InputStream)
                     End If
                     End If
@@ -86,10 +84,9 @@ Public Class MyBitmap
             Pic = My.Application.TryFindResource(FilePathOrResourceName)
             Pic = My.Application.TryFindResource(FilePathOrResourceName)
             If Pic Is Nothing Then
             If Pic Is Nothing Then
                 Pic = New System.Drawing.Bitmap(1, 1)
                 Pic = New System.Drawing.Bitmap(1, 1)
-                Log(ex, $"加载位图失败({FilePathOrResourceName})")
-                Throw
+                Throw New Exception($"加载 MyBitmap 失败({FilePathOrResourceName})", ex)
             Else
             Else
-                Log(ex, $"指定类型有误的位图加载({FilePathOrResourceName})", LogLevel.Developer)
+                Log(ex, $"指定类型有误的 MyBitmap 加载({FilePathOrResourceName})", LogLevel.Developer)
                 Exit Try
                 Exit Try
             End If
             End If
         End Try
         End Try
@@ -116,6 +113,13 @@ Public Class MyBitmap
             Pic = New System.Drawing.Bitmap(MS)
             Pic = New System.Drawing.Bitmap(MS)
         End Using
         End Using
     End Sub
     End Sub
+    Private Class WebPDecoder '将代码隔离在另外一个类中,这样只要不调用这个方法就不会加载 Imazen.WebP.dll
+        Public Shared Function DecodeFromBytes(Bytes As Byte()) As System.Drawing.Bitmap
+            If Is32BitSystem Then Throw New Exception("不支持在 32 位系统下加载 WebP 图片。")
+            Dim Decoder As New Imazen.WebP.SimpleDecoder()
+            Return Decoder.DecodeFromBytes(Bytes, Bytes.Length)
+        End Function
+    End Class
 
 
     ''' <summary>
     ''' <summary>
     ''' 获取裁切的图片,这个方法不会导致原对象改变且会返回一个新的对象。
     ''' 获取裁切的图片,这个方法不会导致原对象改变且会返回一个新的对象。
@@ -152,4 +156,4 @@ Public Class MyBitmap
         End Using
         End Using
     End Sub
     End Sub
 
 
-End Class
+End Class

+ 18 - 13
Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb

@@ -389,7 +389,7 @@ Extracted:
         版本Json中存在多个Forge
         版本Json中存在多个Forge
         Mod过多导致超出ID限制
         Mod过多导致超出ID限制
         NightConfig的Bug
         NightConfig的Bug
-        ShadersMod与Optifine同时安装
+        ShadersMod与OptiFine同时安装
         Forge安装不完整
         Forge安装不完整
         Mod需要Java11
         Mod需要Java11
         Mod缺少前置或MC版本错误
         Mod缺少前置或MC版本错误
@@ -516,7 +516,7 @@ Done:
             If LogMc.Contains("java.lang.ClassNotFoundException: org.spongepowered.asm.launch.MixinTweaker") Then AppendReason(CrashReason.MixinBootstrap缺失)
             If LogMc.Contains("java.lang.ClassNotFoundException: org.spongepowered.asm.launch.MixinTweaker") Then AppendReason(CrashReason.MixinBootstrap缺失)
             If LogMc.Contains("Couldn't set pixel format") Then AppendReason(CrashReason.显卡驱动不支持导致无法设置像素格式)
             If LogMc.Contains("Couldn't set pixel format") Then AppendReason(CrashReason.显卡驱动不支持导致无法设置像素格式)
             If LogMc.Contains("java.lang.OutOfMemoryError") OrElse LogMc.Contains("an out of memory error") Then AppendReason(CrashReason.内存不足)
             If LogMc.Contains("java.lang.OutOfMemoryError") OrElse LogMc.Contains("an out of memory error") Then AppendReason(CrashReason.内存不足)
-            If LogMc.Contains("java.lang.RuntimeException: Shaders Mod detected. Please remove it, OptiFine has built-in support for shaders.") Then AppendReason(CrashReason.ShadersMod与Optifine同时安装)
+            If LogMc.Contains("java.lang.RuntimeException: Shaders Mod detected. Please remove it, OptiFine has built-in support for shaders.") Then AppendReason(CrashReason.ShadersMod与OptiFine同时安装)
             If LogMc.Contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier") Then AppendReason(CrashReason.低版本Forge与高版本Java不兼容)
             If LogMc.Contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier") Then AppendReason(CrashReason.低版本Forge与高版本Java不兼容)
             If LogMc.Contains("1282: Invalid operation") Then AppendReason(CrashReason.光影或资源包导致OpenGL1282错误)
             If LogMc.Contains("1282: Invalid operation") Then AppendReason(CrashReason.光影或资源包导致OpenGL1282错误)
             If LogMc.Contains("signer information does not match signer information of other classes in the same package") Then AppendReason(CrashReason.文件或内容校验失败, If(RegexSeek(LogMc, "(?<=class "")[^']+(?=""'s signer information)"), "").TrimEnd(vbCrLf))
             If LogMc.Contains("signer information does not match signer information of other classes in the same package") Then AppendReason(CrashReason.文件或内容校验失败, If(RegexSeek(LogMc, "(?<=class "")[^']+(?=""'s signer information)"), "").TrimEnd(vbCrLf))
@@ -525,6 +525,7 @@ Done:
             If LogMc.Contains("Unsupported class file major version") Then AppendReason(CrashReason.Java版本不兼容)
             If LogMc.Contains("Unsupported class file major version") Then AppendReason(CrashReason.Java版本不兼容)
             If LogMc.Contains("com.electronwill.nightconfig.core.io.ParsingException: Not enough data available") Then AppendReason(CrashReason.NightConfig的Bug)
             If LogMc.Contains("com.electronwill.nightconfig.core.io.ParsingException: Not enough data available") Then AppendReason(CrashReason.NightConfig的Bug)
             If LogMc.Contains("Cannot find launch target fmlclient, unable to launch") Then AppendReason(CrashReason.Forge安装不完整)
             If LogMc.Contains("Cannot find launch target fmlclient, unable to launch") Then AppendReason(CrashReason.Forge安装不完整)
+            If LogMc.Contains("Invalid paths argument, contained no existing paths") AndAlso LogMc.Contains("libraries\net\minecraftforge\fmlcore") Then AppendReason(CrashReason.Forge安装不完整)
             If LogMc.Contains("Invalid module name: '' is not a Java identifier") Then AppendReason(CrashReason.Mod名称包含特殊字符)
             If LogMc.Contains("Invalid module name: '' is not a Java identifier") Then AppendReason(CrashReason.Mod名称包含特殊字符)
             If LogMc.Contains("has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to") Then AppendReason(CrashReason.Mod需要Java11)
             If LogMc.Contains("has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to") Then AppendReason(CrashReason.Mod需要Java11)
             If LogMc.Contains("java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual") Then AppendReason(CrashReason.Mod需要Java11)
             If LogMc.Contains("java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual") Then AppendReason(CrashReason.Mod需要Java11)
@@ -623,8 +624,11 @@ Done:
         '崩溃报告分析
         '崩溃报告分析
         If LogCrash IsNot Nothing Then
         If LogCrash IsNot Nothing Then
             If LogCrash.Contains("Suspected Mod") Then
             If LogCrash.Contains("Suspected Mod") Then
-                Dim Suspects = RegexSearch(LogCrash.Between("Suspected Mod", "Stacktrace"), "(?<=\n\t[^(\t]+\()[^)\n]+")
-                If Suspects.Any Then AppendReason(CrashReason.怀疑Mod导致游戏崩溃, TryAnalyzeModName(Suspects))
+                Dim SuspectsRaw As String = LogCrash.Between("Suspected Mod", "Stacktrace")
+                If Not SuspectsRaw.StartsWithF("s: None") Then 'Suspected Mods: None
+                    Dim Suspects = RegexSearch(SuspectsRaw, "(?<=\n\t[^(\t]+\()[^)\n]+")
+                    If Suspects.Any Then AppendReason(CrashReason.怀疑Mod导致游戏崩溃, TryAnalyzeModName(Suspects))
+                End If
             End If
             End If
         End If
         End If
 
 
@@ -744,7 +748,7 @@ NextStack:
             '[Fabric] 获取所有包含 Mod 信息的行
             '[Fabric] 获取所有包含 Mod 信息的行
             Dim ModNameLines As New List(Of String)
             Dim ModNameLines As New List(Of String)
             For Each Line In Details.Split(vbLf)
             For Each Line In Details.Split(vbLf)
-                If Line.ContainsF(".jar", True) OrElse
+                If (Line.ContainsF(".jar", True) AndAlso Line.Length - Line.Replace(".jar", "").Length = 4) OrElse '只有一个 .jar
                    (IsFabricDetail AndAlso Line.StartsWithF(vbTab & vbTab) AndAlso Not RegexCheck(Line, "\t\tfabric[\w-]*: Fabric")) Then ModNameLines.Add(Line)
                    (IsFabricDetail AndAlso Line.StartsWithF(vbTab & vbTab) AndAlso Not RegexCheck(Line, "\t\tfabric[\w-]*: Fabric")) Then ModNameLines.Add(Line)
             Next
             Next
             Log("[Crash] 崩溃报告中找到 " & ModNameLines.Count & " 个可能的 Mod 项目行")
             Log("[Crash] 崩溃报告中找到 " & ModNameLines.Count & " 个可能的 Mod 项目行")
@@ -886,6 +890,7 @@ NextStack:
                             WriteFile(TempFolder & "Report\" & FileName,
                             WriteFile(TempFolder & "Report\" & FileName,
                                       SecretFilter(ReadFile(OutputFile, FileEncoding), If(FileName = "启动脚本.bat", "F", "*")),
                                       SecretFilter(ReadFile(OutputFile, FileEncoding), If(FileName = "启动脚本.bat", "F", "*")),
                                       Encoding:=FileEncoding)
                                       Encoding:=FileEncoding)
+                            Log($"[Crash] 导出文件:{FileName},编码:{FileEncoding.HeaderName}")
                         End If
                         End If
                     Next
                     Next
                     '导出报告
                     '导出报告
@@ -942,9 +947,9 @@ NextStack:
                     End If
                     End If
                 Case CrashReason.Mod缺少前置或MC版本错误
                 Case CrashReason.Mod缺少前置或MC版本错误
                     If Additional.Any Then
                     If Additional.Any 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.堆栈分析发现关键字
                 Case CrashReason.堆栈分析发现关键字
                     If Additional.Count = 1 Then
                     If Additional.Count = 1 Then
@@ -980,7 +985,7 @@ NextStack:
                     If Additional.Count = 1 Then
                     If Additional.Count = 1 Then
                         Results.Add("名为 " & Additional.First & " 的 Mod 初始化失败,导致游戏无法继续加载。\n你可以尝试禁用此 Mod,然后观察游戏是否还会崩溃。\n\e\h")
                         Results.Add("名为 " & Additional.First & " 的 Mod 初始化失败,导致游戏无法继续加载。\n你可以尝试禁用此 Mod,然后观察游戏是否还会崩溃。\n\e\h")
                     Else
                     Else
-                        Results.Add("以下 Mod 初始化失败,导致游戏无法继续加载:\n - " & Join(Additional, "\n - ") & "\n\n你可以尝试依次禁用上述 Mod,然后观察游戏是否还会崩溃。\n\e\h")
+                        Results.Add("以下 Mod 初始化失败,导致游戏出错:\n - " & Join(Additional, "\n - ") & "\n\n你可以尝试依次禁用上述 Mod,然后观察游戏是否还会崩溃。\n\e\h")
                     End If
                     End If
                 Case CrashReason.特定方块导致崩溃
                 Case CrashReason.特定方块导致崩溃
                     If Additional.Count = 1 Then
                     If Additional.Count = 1 Then
@@ -992,7 +997,7 @@ NextStack:
                     If Additional.Count >= 2 Then
                     If Additional.Count >= 2 Then
                         Results.Add("你重复安装了多个相同的 Mod:\n - " & Join(Additional, "\n - ") & "\n\n每个 Mod 只能出现一次,请删除重复的 Mod,然后再启动游戏。")
                         Results.Add("你重复安装了多个相同的 Mod:\n - " & Join(Additional, "\n - ") & "\n\n每个 Mod 只能出现一次,请删除重复的 Mod,然后再启动游戏。")
                     Else
                     Else
-                        Results.Add("你可能重复安装了多个相同的 Mod,导致游戏无法继续加载。\n\n每个 Mod 只能出现一次,请删除重复的 Mod,然后再启动游戏。\e\h")
+                        Results.Add("你可能重复安装了多个相同的 Mod,导致游戏出错。\n\n每个 Mod 只能出现一次,请删除重复的 Mod,然后再启动游戏。\e\h")
                     End If
                     End If
                 Case CrashReason.特定实体导致崩溃
                 Case CrashReason.特定实体导致崩溃
                     If Additional.Count = 1 Then
                     If Additional.Count = 1 Then
@@ -1002,12 +1007,12 @@ NextStack:
                     End If
                     End If
                 Case CrashReason.OptiFine与Forge不兼容
                 Case CrashReason.OptiFine与Forge不兼容
                     Results.Add("由于 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n\n请前往 OptiFine 官网(https://optifine.net/downloads)查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏。")
                     Results.Add("由于 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n\n请前往 OptiFine 官网(https://optifine.net/downloads)查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏。")
-                Case CrashReason.ShadersMod与Optifine同时安装
-                    Results.Add("无需同时安装 Optifine 和 Shaders Mod,Optifine 已经集成了 Shaders Mod 的功能。\n在删除 Shaders Mod 后,游戏即可正常运行。")
+                Case CrashReason.ShadersMod与OptiFine同时安装
+                    Results.Add("无需同时安装 Optifine 和 Shaders Mod,OptiFine 已经集成了 Shaders Mod 的功能。\n在删除 Shaders Mod 后,游戏即可正常运行。")
                 Case CrashReason.低版本Forge与高版本Java不兼容
                 Case CrashReason.低版本Forge与高版本Java不兼容
                     Results.Add("由于低版本 Forge 与当前 Java 不兼容,导致了游戏崩溃。\n\n请尝试以下解决方案:\n - 更新 Forge 到 36.2.26 或更高版本\n - 换用版本低于 1.8.0.320 的 Java")
                     Results.Add("由于低版本 Forge 与当前 Java 不兼容,导致了游戏崩溃。\n\n请尝试以下解决方案:\n - 更新 Forge 到 36.2.26 或更高版本\n - 换用版本低于 1.8.0.320 的 Java")
                 Case CrashReason.版本Json中存在多个Forge
                 Case CrashReason.版本Json中存在多个Forge
-                    Results.Add("可能由于使用其他启动器修改了 Forge 版本,当前版本的文件存在异常,导致了游戏崩溃。\n请尝试重新全新安装 Forge,而非使用其他启动器修改 Forge 版本。")
+                    Results.Add("可能由于其他启动器修改了 Forge 版本,当前版本的文件存在异常,导致了游戏崩溃。\n请尝试重新全新安装 Forge,而非使用其他启动器修改 Forge 版本。")
                 Case CrashReason.玩家手动触发调试崩溃
                 Case CrashReason.玩家手动触发调试崩溃
                     Results.Add("* 事实上,你的游戏没有任何问题,这是你自己触发的崩溃。\n* 你难道没有更重要的事要做吗?")
                     Results.Add("* 事实上,你的游戏没有任何问题,这是你自己触发的崩溃。\n* 你难道没有更重要的事要做吗?")
                 Case CrashReason.Mod需要Java11
                 Case CrashReason.Mod需要Java11
@@ -1033,7 +1038,7 @@ NextStack:
                 Case CrashReason.文件或内容校验失败
                 Case CrashReason.文件或内容校验失败
                     Results.Add("部分文件或内容校验失败,导致游戏出现了问题。\n\n请尝试删除游戏(包括 Mod)并重新下载,或尝试在重新下载时使用 VPN。\h")
                     Results.Add("部分文件或内容校验失败,导致游戏出现了问题。\n\n请尝试删除游戏(包括 Mod)并重新下载,或尝试在重新下载时使用 VPN。\h")
                 Case CrashReason.Forge安装不完整
                 Case CrashReason.Forge安装不完整
-                    Results.Add("由于 Forge 安装不完整,导致游戏无法正常运行。\n请尝试重新安装 Forge。\h")
+                    Results.Add("由于安装的 Forge 文件丢失,导致游戏无法正常运行。\n请重新安装一次相同版本的 Forge,然后再启动游戏。\n在打包游戏时删除 libraries 文件夹可能导致此错误。\h")
                 Case CrashReason.Fabric报错
                 Case CrashReason.Fabric报错
                     If Additional.Count = 1 Then
                     If Additional.Count = 1 Then
                         Results.Add("Fabric 提供了以下错误信息:\n" & Additional.First & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
                         Results.Add("Fabric 提供了以下错误信息:\n" & Additional.First & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")

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

@@ -378,7 +378,7 @@ NextInner:
         '根据当前登录方式优先返回
         '根据当前登录方式优先返回
         Select Case Setup.Get("LoginType")
         Select Case Setup.Get("LoginType")
             Case McLoginType.Ms
             Case McLoginType.Ms
-                If Setup.Get("CacheMsName") <> "" Then Return Setup.Get("CacheMsName")
+                If Setup.Get("CacheMsV2Name") <> "" Then Return Setup.Get("CacheMsV2Name")
             Case McLoginType.Legacy
             Case McLoginType.Legacy
                 If Setup.Get("LoginLegacyName") <> "" Then Return Setup.Get("LoginLegacyName").ToString.Before("¨")
                 If Setup.Get("LoginLegacyName") <> "" Then Return Setup.Get("LoginLegacyName").ToString.Before("¨")
             Case McLoginType.Nide
             Case McLoginType.Nide
@@ -387,7 +387,7 @@ NextInner:
                 If Setup.Get("CacheAuthName") <> "" Then Return Setup.Get("CacheAuthName")
                 If Setup.Get("CacheAuthName") <> "" Then Return Setup.Get("CacheAuthName")
         End Select
         End Select
         '查找所有可能的项
         '查找所有可能的项
-        If Setup.Get("CacheMsName") <> "" Then Return Setup.Get("CacheMsName")
+        If Setup.Get("CacheMsV2Name") <> "" Then Return Setup.Get("CacheMsV2Name")
         If Setup.Get("CacheNideName") <> "" Then Return Setup.Get("CacheNideName")
         If Setup.Get("CacheNideName") <> "" Then Return Setup.Get("CacheNideName")
         If Setup.Get("CacheAuthName") <> "" Then Return Setup.Get("CacheAuthName")
         If Setup.Get("CacheAuthName") <> "" Then Return Setup.Get("CacheAuthName")
         If Setup.Get("LoginLegacyName") <> "" Then Return Setup.Get("LoginLegacyName").ToString.Before("¨")
         If Setup.Get("LoginLegacyName") <> "" Then Return Setup.Get("LoginLegacyName").ToString.Before("¨")
@@ -399,7 +399,7 @@ NextInner:
     Public Function McLoginAble() As String
     Public Function McLoginAble() As String
         Select Case Setup.Get("LoginType")
         Select Case Setup.Get("LoginType")
             Case McLoginType.Ms
             Case McLoginType.Ms
-                If Setup.Get("CacheMsOAuthRefresh") = "" Then
+                If Setup.Get("CacheMsV2OAuthRefresh") = "" Then
                     Return FrmLoginMs.IsVaild()
                     Return FrmLoginMs.IsVaild()
                 Else
                 Else
                     Return ""
                     Return ""
@@ -450,7 +450,7 @@ NextInner:
                 Case McLoginType.Legacy
                 Case McLoginType.Legacy
                     LoginData = PageLoginLegacy.GetLoginData()
                     LoginData = PageLoginLegacy.GetLoginData()
                 Case McLoginType.Ms
                 Case McLoginType.Ms
-                    If Setup.Get("CacheMsOAuthRefresh") = "" Then
+                    If Setup.Get("CacheMsV2OAuthRefresh") = "" Then
                         LoginData = PageLoginMs.GetLoginData()
                         LoginData = PageLoginMs.GetLoginData()
                     Else
                     Else
                         LoginData = PageLoginMsSkin.GetLoginData()
                         LoginData = PageLoginMsSkin.GetLoginData()
@@ -552,11 +552,11 @@ Relogin:
         Dim Result = MsLoginStep6(AccessToken)
         Dim Result = MsLoginStep6(AccessToken)
         Data.Progress = 0.98
         Data.Progress = 0.98
         '输出登录结果
         '输出登录结果
-        Setup.Set("CacheMsOAuthRefresh", OAuthRefreshToken)
-        Setup.Set("CacheMsAccess", AccessToken)
-        Setup.Set("CacheMsUuid", Result(0))
-        Setup.Set("CacheMsName", Result(1))
-        Setup.Set("CacheMsProfileJson", Result(2))
+        Setup.Set("CacheMsV2OAuthRefresh", OAuthRefreshToken)
+        Setup.Set("CacheMsV2Access", AccessToken)
+        Setup.Set("CacheMsV2Uuid", Result(0))
+        Setup.Set("CacheMsV2Name", Result(1))
+        Setup.Set("CacheMsV2ProfileJson", Result(2))
         Dim MsJson As JObject = GetJson(Setup.Get("LoginMsJson"))
         Dim MsJson As JObject = GetJson(Setup.Get("LoginMsJson"))
         MsJson.Remove(Input.UserName) '如果更改了玩家名……
         MsJson.Remove(Input.UserName) '如果更改了玩家名……
         MsJson(Result(1)) = OAuthRefreshToken
         MsJson(Result(1)) = OAuthRefreshToken
@@ -850,7 +850,7 @@ Retry:
         While Converter.Result Is Nothing
         While Converter.Result Is Nothing
             Thread.Sleep(100)
             Thread.Sleep(100)
         End While
         End While
-        If TypeOf Converter.Result Is RetryException Then
+        If TypeOf Converter.Result Is RestartException Then
             If MyMsgBox($"请在登录时选择 {vbLQ}其他登录方法{vbRQ},然后选择 {vbLQ}使用我的密码{vbRQ}。{vbCrLf}如果没有该选项,请选择 {vbLQ}设置密码{vbRQ},设置完毕后再登录。",
             If MyMsgBox($"请在登录时选择 {vbLQ}其他登录方法{vbRQ},然后选择 {vbLQ}使用我的密码{vbRQ}。{vbCrLf}如果没有该选项,请选择 {vbLQ}设置密码{vbRQ},设置完毕后再登录。",
                 "需要使用密码登录", "重新登录", "设置密码", "取消",
                 "需要使用密码登录", "重新登录", "设置密码", "取消",
                 Button2Action:=Sub() OpenWebsite("https://account.live.com/password/Change")) = 1 Then
                 Button2Action:=Sub() OpenWebsite("https://account.live.com/password/Change")) = 1 Then
@@ -1262,8 +1262,7 @@ Retry:
     ''' 释放 Java Wrapper 并返回完整文件路径。
     ''' 释放 Java Wrapper 并返回完整文件路径。
     ''' </summary>
     ''' </summary>
     Public Function ExtractJavaWrapper() As String
     Public Function ExtractJavaWrapper() As String
-        Dim BaseDir As String = GetPureASCIIDir()
-        Dim WrapperPath As String = BaseDir & "\JavaWrapper.jar"
+        Dim WrapperPath As String = PathPure & "JavaWrapper.jar"
         Log("[Java] 选定的 Java Wrapper 路径:" & WrapperPath)
         Log("[Java] 选定的 Java Wrapper 路径:" & WrapperPath)
         SyncLock ExtractJavaWrapperLock '避免 OptiFine 和 Forge 安装时同时释放 Java Wrapper 导致冲突
         SyncLock ExtractJavaWrapperLock '避免 OptiFine 和 Forge 安装时同时释放 Java Wrapper 导致冲突
             Try
             Try
@@ -1277,7 +1276,7 @@ Retry:
                         WriteFile(WrapperPath, GetResources("JavaWrapper"))
                         WriteFile(WrapperPath, GetResources("JavaWrapper"))
                     Catch ex2 As Exception
                     Catch ex2 As Exception
                         Log(ex2, "Java Wrapper 文件重新释放失败,将尝试更换文件名重新生成", LogLevel.Developer)
                         Log(ex2, "Java Wrapper 文件重新释放失败,将尝试更换文件名重新生成", LogLevel.Developer)
-                        WrapperPath = BaseDir & "\JavaWrapper2.jar"
+                        WrapperPath = PathPure & "JavaWrapper2.jar"
                         Try
                         Try
                             WriteFile(WrapperPath, GetResources("JavaWrapper"))
                             WriteFile(WrapperPath, GetResources("JavaWrapper"))
                         Catch ex3 As Exception
                         Catch ex3 As Exception
@@ -1293,22 +1292,6 @@ Retry:
     End Function
     End Function
     Private ExtractJavaWrapperLock As New Object
     Private ExtractJavaWrapperLock As New Object
 
 
-    ''' <summary>
-    ''' 获取一个可用于临时存放文件的,不含任何特殊字符的文件夹路径。
-    ''' 返回值不以 \ 结尾。
-    ''' </summary>
-    Public Function GetPureASCIIDir() As String
-        If (Path & "PCL").IsASCII() Then
-            Return Path & "PCL"
-        ElseIf PathAppdata.IsASCII() Then
-            Return PathAppdata.TrimEnd("\")
-        ElseIf PathTemp.IsASCII() Then
-            Return PathTemp.TrimEnd("\")
-        Else
-            Return OsDrive & "ProgramData\PCL"
-        End If
-    End Function
-
     '主方法,合并 Jvm、Game、Replace 三部分的参数数据
     '主方法,合并 Jvm、Game、Replace 三部分的参数数据
     Private Sub McLaunchArgumentMain(Loader As LoaderTask(Of String, List(Of McLibToken)))
     Private Sub McLaunchArgumentMain(Loader As LoaderTask(Of String, List(Of McLibToken)))
         McLaunchLog("开始获取 Minecraft 启动参数")
         McLaunchLog("开始获取 Minecraft 启动参数")
@@ -1408,7 +1391,7 @@ Retry:
                 Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent))
                 Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent))
             Try
             Try
                 Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8)
                 Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8)
-                DataList.Insert(0, "-javaagent:""" & PathAppdata & "authlib-injector.jar""=" & Server &
+                DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server &
                               " -Dauthlibinjector.side=client" &
                               " -Dauthlibinjector.side=client" &
                               " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
                               " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
             Catch ex As Exception
             Catch ex As Exception
@@ -1418,7 +1401,7 @@ Retry:
 
 
         '添加 Java Wrapper 作为主 Jar
         '添加 Java Wrapper 作为主 Jar
         If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
         If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
-        DataList.Add("-Doolloo.jlw.tmpdir=""" & GetPureASCIIDir() & """")
+        DataList.Add("-Doolloo.jlw.tmpdir=""" & PathPure.TrimEnd("\") & """")
         DataList.Add("-jar """ & ExtractJavaWrapper() & """")
         DataList.Add("-jar """ & ExtractJavaWrapper() & """")
 
 
         '添加 MainClass
         '添加 MainClass
@@ -1475,7 +1458,7 @@ NextVersion:
                 Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent))
                 Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent))
             Try
             Try
                 Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8)
                 Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8)
-                DataList.Insert(0, "-javaagent:""" & PathAppdata & "authlib-injector.jar""=" & Server &
+                DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server &
                               " -Dauthlibinjector.side=client" &
                               " -Dauthlibinjector.side=client" &
                               " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
                               " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
             Catch ex As Exception
             Catch ex As Exception
@@ -1485,7 +1468,7 @@ NextVersion:
 
 
         '添加 Java Wrapper 作为主 Jar
         '添加 Java Wrapper 作为主 Jar
         If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
         If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
-        DataList.Add("-Doolloo.jlw.tmpdir=""" & GetPureASCIIDir() & """")
+        DataList.Add("-Doolloo.jlw.tmpdir=""" & PathPure.TrimEnd("\") & """")
         DataList.Add("-jar """ & ExtractJavaWrapper() & """")
         DataList.Add("-jar """ & ExtractJavaWrapper() & """")
 
 
         '将 "-XXX" 与后面 "XXX" 合并到一起
         '将 "-XXX" 与后面 "XXX" 合并到一起

+ 17 - 20
Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb

@@ -1256,9 +1256,8 @@ OnLoaded:
         Dim VersionList As New List(Of McVersion)
         Dim VersionList As New List(Of McVersion)
 
 
 #Region "循环加载每个版本的信息"
 #Region "循环加载每个版本的信息"
-        Dim Dirs As DirectoryInfo() = New DirectoryInfo(Path & "versions").GetDirectories
-        For Each Folder As DirectoryInfo In Dirs
-            If Not Folder.EnumerateFiles.Any Then
+        For Each Folder As DirectoryInfo In New DirectoryInfo(Path & "versions").GetDirectories
+            If Not Folder.Exists OrElse Not Folder.EnumerateFiles.Any Then
                 Log("[Minecraft] 跳过空文件夹:" & Folder.FullName)
                 Log("[Minecraft] 跳过空文件夹:" & Folder.FullName)
                 Continue For
                 Continue For
             End If
             End If
@@ -1528,7 +1527,7 @@ OnLoaded:
     ''' 要求玩家选择一个皮肤文件,并进行相关校验。
     ''' 要求玩家选择一个皮肤文件,并进行相关校验。
     ''' </summary>
     ''' </summary>
     Public Function McSkinSelect() As McSkinInfo
     Public Function McSkinSelect() As McSkinInfo
-        Dim FileName As String = SelectFile("皮肤文件(*.png)|*.png", "选择皮肤文件")
+        Dim FileName As String = SelectFile("皮肤文件(*.png;*.jpg;*.webp)|*.png;*.jpg;*.webp", "选择皮肤文件")
 
 
         '验证有效性
         '验证有效性
         If FileName = "" Then Return New McSkinInfo With {.IsVaild = False}
         If FileName = "" Then Return New McSkinInfo With {.IsVaild = False}
@@ -1935,7 +1934,7 @@ OnLoaded:
     ''' <summary>
     ''' <summary>
     ''' 获取版本缺失的支持库文件所对应的 NetTaskFile。
     ''' 获取版本缺失的支持库文件所对应的 NetTaskFile。
     ''' </summary>
     ''' </summary>
-    Public Function McLibFix(Version As McVersion, Optional CoreJarOnly As Boolean = False) As List(Of NetFile)
+    Public Function McLibFix(Version As McVersion) As List(Of NetFile)
         If Not Version.IsLoaded Then Version.Load() '确保例如 JumpLoader 等项被合并入 Json
         If Not Version.IsLoaded Then Version.Load() '确保例如 JumpLoader 等项被合并入 Json
         Dim Result As New List(Of NetFile)
         Dim Result As New List(Of NetFile)
 
 
@@ -1948,7 +1947,6 @@ OnLoaded:
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "版本缺失主 jar 文件所必须的信息", LogLevel.Developer)
             Log(ex, "版本缺失主 jar 文件所必须的信息", LogLevel.Developer)
         End Try
         End Try
-        If CoreJarOnly Then Return Result
 
 
         'Library 文件
         'Library 文件
         Result.AddRange(McLibFixFromLibToken(McLibListGet(Version, False), JumpLoaderFolder:=Version.PathIndie & ".jumploader\"))
         Result.AddRange(McLibFixFromLibToken(McLibListGet(Version, False), JumpLoaderFolder:=Version.PathIndie & ".jumploader\"))
@@ -1957,15 +1955,15 @@ OnLoaded:
         If Setup.Get("VersionServerLogin", Version:=Version) = 3 Then
         If Setup.Get("VersionServerLogin", Version:=Version) = 3 Then
             Dim TargetFile = PathAppdata & "nide8auth.jar"
             Dim TargetFile = PathAppdata & "nide8auth.jar"
             Dim DownloadInfo As JObject = Nothing
             Dim DownloadInfo As JObject = Nothing
-                '获取下载信息
-                Try
-                    Log("[Minecraft] 开始获取统一通行证下载信息")
-                    '测试链接:https://auth.mc-user.com:233/00000000000000000000000000000000/
-                    DownloadInfo = GetJson(NetGetCodeByDownload({
+            '获取下载信息
+            Try
+                Log("[Minecraft] 开始获取统一通行证下载信息")
+                '测试链接:https://auth.mc-user.com:233/00000000000000000000000000000000/
+                DownloadInfo = GetJson(NetGetCodeByDownload({
                         "https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version)}, IsJson:=True))
                         "https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version)}, IsJson:=True))
-                Catch ex As Exception
-                    Log(ex, "获取统一通行证下载信息失败")
-                End Try
+            Catch ex As Exception
+                Log(ex, "获取统一通行证下载信息失败")
+            End Try
             '校验文件
             '校验文件
             If DownloadInfo IsNot Nothing Then
             If DownloadInfo IsNot Nothing Then
                 Dim Checker As New FileChecker(Hash:=DownloadInfo("jarHash").ToString)
                 Dim Checker As New FileChecker(Hash:=DownloadInfo("jarHash").ToString)
@@ -1978,9 +1976,8 @@ OnLoaded:
         End If
         End If
 
 
         'Authlib-Injector 文件
         'Authlib-Injector 文件
-        If Setup.Get("VersionServerLogin", Version:=Version) = 4 OrElse
-           (PageLinkHiper.HiperState = LoadState.Finished AndAlso Setup.Get("LoginType") = McLoginType.Legacy) Then 'HiPer 登录转接
-            Dim TargetFile = PathAppdata & "authlib-injector.jar"
+        If Setup.Get("VersionServerLogin", Version:=Version) = 4 Then
+            Dim TargetFile = PathPure & "\authlib-injector.jar"
             Dim DownloadInfo As JObject = Nothing
             Dim DownloadInfo As JObject = Nothing
             '获取下载信息
             '获取下载信息
             Try
             Try
@@ -2001,9 +1998,9 @@ OnLoaded:
                             Replace("bmclapi2.bangbang93.com/mirrors/authlib-injector", "authlib-injector.yushi.moe")
                             Replace("bmclapi2.bangbang93.com/mirrors/authlib-injector", "authlib-injector.yushi.moe")
                     Log("[Minecraft] Authlib-Injector 需要更新:" & DownloadAddress, LogLevel.Developer)
                     Log("[Minecraft] Authlib-Injector 需要更新:" & DownloadAddress, LogLevel.Developer)
                     Result.Add(New NetFile({
                     Result.Add(New NetFile({
-                            DownloadAddress,
-                            DownloadAddress.Replace("authlib-injector.yushi.moe", "bmclapi2.bangbang93.com/mirrors/authlib-injector")
-                        }, TargetFile, New FileChecker(Hash:=DownloadInfo("checksums")("sha256").ToString)))
+                        DownloadAddress,
+                        DownloadAddress.Replace("authlib-injector.yushi.moe", "bmclapi2.bangbang93.com/mirrors/authlib-injector")
+                    }, TargetFile, New FileChecker(Hash:=DownloadInfo("checksums")("sha256").ToString)))
                 End If
                 End If
             End If
             End If
         End If
         End If

+ 1 - 0
Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb

@@ -825,6 +825,7 @@ Finished:
                 Finally
                 Finally
                     RunInUiWait(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.Text = "正在加载 Mod 列表")
                     RunInUiWait(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.Text = "正在加载 Mod 列表")
                 End Try
                 End Try
+                FrmVersionMod.LoaderRun(LoaderFolderRunType.UpdateOnly)
             End If
             End If
 
 
             '获取 Mod 文件夹下的可用文件列表
             '获取 Mod 文件夹下的可用文件列表

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

@@ -342,8 +342,8 @@ Retry:
             End If
             End If
             '删除原始整合包文件
             '删除原始整合包文件
             For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
             For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
-                If Not Setup.Get("ToolDownloadKeepModpack") AndAlso File.Exists(Target) Then
-                    Log("[ModPack] 根据设置要求删除原始整合包文件:" & Target)
+                If File.Exists(Target) Then
+                    Log("[ModPack] 删除原始整合包文件:" & Target)
                     File.Delete(Target)
                     File.Delete(Target)
                 End If
                 End If
             Next
             Next
@@ -491,8 +491,8 @@ Retry:
             End If
             End If
             '删除原始整合包文件
             '删除原始整合包文件
             For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
             For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
-                If Not Setup.Get("ToolDownloadKeepModpack") AndAlso File.Exists(Target) Then
-                    Log("[ModPack] 根据设置要求删除原始整合包文件:" & Target)
+                If File.Exists(Target) Then
+                    Log("[ModPack] 删除原始整合包文件:" & Target)
                     File.Delete(Target)
                     File.Delete(Target)
                 End If
                 End If
             Next
             Next

+ 2 - 1
Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml

@@ -1,6 +1,7 @@
 <Grid x:Class="MyCompItem"
 <Grid x:Class="MyCompItem"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:local="clr-namespace:PCL"
              x:Name="PanBack" Height="64"
              x:Name="PanBack" Height="64"
              RenderTransformOrigin="0.5,0.5" Background="{StaticResource ColorBrushSemiTransparent}" SnapsToDevicePixels="True">
              RenderTransformOrigin="0.5,0.5" Background="{StaticResource ColorBrushSemiTransparent}" SnapsToDevicePixels="True">
     <Grid.ColumnDefinitions>
     <Grid.ColumnDefinitions>
@@ -23,7 +24,7 @@
         <Border.Clip>
         <Border.Clip>
             <RectangleGeometry Rect="0,0,50,50" RadiusX="6" RadiusY="6" />
             <RectangleGeometry Rect="0,0,50,50" RadiusX="6" RadiusY="6" />
         </Border.Clip>
         </Border.Clip>
-        <Image x:Name="PathLogo" RenderOptions.BitmapScalingMode="HighQuality" />
+        <local:MyImage x:Name="PathLogo" RenderOptions.BitmapScalingMode="HighQuality" FallbackSource="pack://application:,,,/images/Icons/NoIcon.png" />
     </Border>
     </Border>
     <!-- 标题 -->
     <!-- 标题 -->
     <StackPanel Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="1" VerticalAlignment="Bottom" Margin="0,1,0,0" Orientation="Horizontal" 
     <StackPanel Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="1" VerticalAlignment="Bottom" Margin="0,1,0,0" Orientation="Horizontal" 

+ 2 - 75
Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb

@@ -4,87 +4,14 @@
     Public Uuid As Integer = GetUuid()
     Public Uuid As Integer = GetUuid()
 
 
     'Logo
     'Logo
-    Private _Logo As String = ""
     Public Property Logo As String
     Public Property Logo As String
         Get
         Get
-            Return _Logo
+            Return PathLogo.Source
         End Get
         End Get
         Set(value As String)
         Set(value As String)
-            If _Logo = value OrElse value Is Nothing Then Exit Property
-            _Logo = value
-            Dim FileAddress = PathTemp & "CompLogo\" & GetHash(_Logo) & ".png"
-            Try
-                If _Logo.StartsWithF("http", True) Then
-                    '网络图片
-                    If File.Exists(FileAddress) Then
-                        PathLogo.Source = New MyBitmap(FileAddress)
-                    Else
-                        PathLogo.Source = New MyBitmap(PathImage & "Icons/NoIcon.png")
-                        RunInNewThread(Sub() LogoLoader(FileAddress), "Comp Logo Loader " & Uuid & "#", ThreadPriority.BelowNormal)
-                    End If
-                Else
-                    '位图
-                    PathLogo.Source = New MyBitmap(_Logo)
-                End If
-            Catch ex As IOException
-                Log(ex, "加载资源工程图标时读取失败(" & FileAddress & ")")
-            Catch ex As ArgumentException
-                '考虑缓存的图片本身可能有误
-                Log(ex, "可视化资源工程图标失败(" & FileAddress & ")")
-                Try
-                    File.Delete(FileAddress)
-                    Log("[Comp] 已清理损坏的资源工程图标:" & FileAddress)
-                Catch exx As Exception
-                    Log(exx, "清理损坏的资源工程图标缓存失败(" & FileAddress & ")", LogLevel.Hint)
-                End Try
-            Catch ex As Exception
-                Log(ex, "加载资源工程图标失败(" & value & ")")
-            End Try
+            PathLogo.Source = value
         End Set
         End Set
     End Property
     End Property
-    '后台加载 Logo
-    Private Sub LogoLoader(LocalFileAddress As String)
-        Dim Retried As Boolean = False
-        Dim DownloadEnd As String = GetUuid()
-RetryStart:
-        Try
-            'CurseForge 图片使用缩略图
-            Dim Url As String = _Logo
-            If Url.Contains("/256/256/") AndAlso GetPixelSize(1) <= 1.25 AndAlso Not Retried Then
-                Url = Url.Replace("/256/256/", "/64/64/") '#3075:部分 Mod 不存在 64x64 图标,所以重试时不再缩小
-            End If
-            '下载图片
-            NetDownload(Url, LocalFileAddress & DownloadEnd, True)
-            Dim LoadError As Exception = Nothing
-            RunInUiWait(
-            Sub()
-                Try
-                    '在地址更换时取消加载
-                    If LocalFileAddress <> $"{PathTemp}CompLogo\{GetHash(_Logo)}.png" Then Exit Sub
-                    '在完成正常加载后才保存缓存图片
-                    PathLogo.Source = New MyBitmap(LocalFileAddress & DownloadEnd)
-                Catch ex As Exception
-                    Log(ex, $"读取资源工程图标失败({LocalFileAddress})")
-                    File.Delete(LocalFileAddress & DownloadEnd)
-                    LoadError = ex
-                End Try
-            End Sub)
-            If LoadError IsNot Nothing Then Throw LoadError
-            If File.Exists(LocalFileAddress) Then
-                File.Delete(LocalFileAddress & DownloadEnd)
-            Else
-                FileIO.FileSystem.MoveFile(LocalFileAddress & DownloadEnd, LocalFileAddress)
-            End If
-        Catch ex As Exception
-            If Not Retried Then
-                Retried = True
-                GoTo RetryStart
-            Else
-                Log(ex, $"下载资源工程图标失败({_Logo})")
-                RunInUi(Sub() PathLogo.Source = New MyBitmap(PathImage & "Icons/NoIcon.png"))
-            End If
-        End Try
-    End Sub
 
 
     '标题
     '标题
     Public Property Title As String
     Public Property Title As String

+ 2 - 2
Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml

@@ -24,7 +24,7 @@
         <Border.Clip>
         <Border.Clip>
             <RectangleGeometry Rect="0,0,34,34" RadiusX="6" RadiusY="6" />
             <RectangleGeometry Rect="0,0,34,34" RadiusX="6" RadiusY="6" />
         </Border.Clip>
         </Border.Clip>
-        <Image x:Name="PathLogo" RenderOptions.BitmapScalingMode="HighQuality" />
+        <local:MyImage x:Name="PathLogo" RenderOptions.BitmapScalingMode="HighQuality" FallbackSource="pack://application:,,,/images/Icons/NoIcon.png" />
     </Border>
     </Border>
     <!-- 标题 -->
     <!-- 标题 -->
     <Grid Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="1" VerticalAlignment="Bottom" Margin="0,1,0,0"
     <Grid Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="1" VerticalAlignment="Bottom" Margin="0,1,0,0"
@@ -52,6 +52,6 @@
         </Border>-->
         </Border>-->
     </StackPanel>
     </StackPanel>
     <TextBlock x:Name="LabInfo" Grid.Row="2" Grid.Column="4" VerticalAlignment="Bottom" Margin="0,0,3,1"
     <TextBlock x:Name="LabInfo" Grid.Row="2" Grid.Column="4" VerticalAlignment="Bottom" Margin="0,0,3,1"
-               TextTrimming="CharacterEllipsis" FontSize="12" Foreground="{StaticResource ColorBrushGray3}"
+               TextTrimming="CharacterEllipsis" FontSize="12" Foreground="{StaticResource ColorBrushGray4}"
                IsHitTestVisible="False" SnapsToDevicePixels="False" UseLayoutRounding="False" />
                IsHitTestVisible="False" SnapsToDevicePixels="False" UseLayoutRounding="False" />
 </Grid>
 </Grid>

+ 44 - 98
Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb

@@ -6,88 +6,14 @@ Public Class MyLocalModItem
     Public Uuid As Integer = GetUuid()
     Public Uuid As Integer = GetUuid()
 
 
     'Logo
     'Logo
-    Private _Logo As String = ""
     Public Property Logo As String
     Public Property Logo As String
         Get
         Get
-            Return _Logo
+            Return PathLogo.Source
         End Get
         End Get
         Set(value As String)
         Set(value As String)
-            If _Logo = value OrElse value Is Nothing Then Exit Property
-            _Logo = value
-            If ModeDebug AndAlso Not _Logo = PathImage & "Icons/NoIcon.png" Then Log($"[LocalModItem] Mod {Title} 的图标:{value}")
-            Dim FileAddress = PathTemp & "CompLogo\" & GetHash(_Logo) & ".png"
-            Try
-                If _Logo.StartsWithF("http", True) Then
-                    '网络图片
-                    If File.Exists(FileAddress) Then
-                        PathLogo.Source = New MyBitmap(FileAddress)
-                    Else
-                        PathLogo.Source = New MyBitmap(PathImage & "Icons/NoIcon.png")
-                        RunInNewThread(Sub() LogoLoader(FileAddress), "Comp Logo Loader " & Uuid & "#", ThreadPriority.BelowNormal)
-                    End If
-                Else
-                    '位图
-                    PathLogo.Source = New MyBitmap(_Logo)
-                End If
-            Catch ex As IOException
-                Log(ex, "加载本地 Mod 图标时读取失败(" & FileAddress & ")")
-            Catch ex As ArgumentException
-                '考虑缓存的图片本身可能有误
-                Log(ex, "可视化本地 Mod 图标失败(" & FileAddress & ")")
-                Try
-                    File.Delete(FileAddress)
-                    Log("[LocalModItem] 已清理损坏的本地 Mod 图标:" & FileAddress)
-                Catch exx As Exception
-                    Log(exx, "清理损坏的本地 Mod 图标缓存失败(" & FileAddress & ")", LogLevel.Hint)
-                End Try
-            Catch ex As Exception
-                Log(ex, "加载本地 Mod 图标失败(" & value & ")")
-            End Try
+            PathLogo.Source = value
         End Set
         End Set
     End Property
     End Property
-    '后台加载 Logo
-    Private Sub LogoLoader(LocalFileAddress As String)
-        Dim Retried As Boolean = False
-        Dim DownloadEnd As String = GetUuid()
-RetryStart:
-        Try
-            'CurseForge 图片使用缩略图
-            Dim Url As String = _Logo
-            If Url.Contains("/256/256/") AndAlso GetPixelSize(1) <= 1.25 AndAlso Not Retried Then '#3075:部分 Mod 不存在 64x64 图标,所以重试时不再缩小
-                Url = Url.Replace("/256/256/", "/64/64/")
-            End If
-            '下载图片
-            NetDownload(Url, LocalFileAddress & DownloadEnd, True)
-            Dim LoadError As Exception = Nothing
-            RunInUiWait(
-            Sub()
-                Try
-                    '在地址更换时取消加载
-                    If LocalFileAddress <> $"{PathTemp}CompLogo\{GetHash(_Logo)}.png" Then Exit Sub
-                    '在完成正常加载后才保存缓存图片
-                    PathLogo.Source = New MyBitmap(LocalFileAddress & DownloadEnd)
-                Catch ex As Exception
-                    Log(ex, "读取本地 Mod 图标失败(" & LocalFileAddress & ")")
-                    File.Delete(LocalFileAddress & DownloadEnd)
-                    LoadError = ex
-                End Try
-            End Sub)
-            If LoadError IsNot Nothing Then Throw LoadError
-            If File.Exists(LocalFileAddress) Then
-                File.Delete(LocalFileAddress & DownloadEnd)
-            Else
-                FileIO.FileSystem.MoveFile(LocalFileAddress & DownloadEnd, LocalFileAddress)
-            End If
-        Catch ex As Exception
-            If Not Retried Then
-                Retried = True
-                GoTo RetryStart
-            Else
-                Log(ex, $"下载本地 Mod 图标失败({_Logo})")
-                RunInUi(Sub() PathLogo.Source = New MyBitmap(PathImage & "Icons/NoIcon.png"))
-            End If
-        End Try
-    End Sub
 
 
     '标题
     '标题
     Private _Title As String
     Private _Title As String
@@ -143,9 +69,9 @@ RetryStart:
             For Each TagText In value
             For Each TagText In value
                 Dim NewTag = GetObjectFromXML(
                 Dim NewTag = GetObjectFromXML(
                 "<Border xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
                 "<Border xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
-                         Background=""#11000000"" Padding=""3,1"" CornerRadius=""3"" Margin=""0,0,3,0"" 
+                         Background=""#0C000000"" Padding=""3,1"" CornerRadius=""3"" Margin=""0,0,3,0"" 
                          SnapsToDevicePixels=""True"" UseLayoutRounding=""False"">
                          SnapsToDevicePixels=""True"" UseLayoutRounding=""False"">
-                   <TextBlock Text=""" & TagText & """ Foreground=""#868686"" FontSize=""11"" />
+                   <TextBlock Text=""" & TagText & """ Foreground=""#88000000"" FontSize=""11"" />
                 </Border>")
                 </Border>")
                 PanTags.Children.Add(NewTag)
                 PanTags.Children.Add(NewTag)
             Next
             Next
@@ -377,7 +303,7 @@ RetryStart:
 
 
 #End Region
 #End Region
 
 
-    Public Sub Refresh()
+    Public Sub Refresh() Handles Me.Loaded
         RunInUi(
         RunInUi(
         Sub()
         Sub()
             '更新
             '更新
@@ -404,30 +330,45 @@ RetryStart:
             Else
             Else
                 BtnUpdate.Visibility = Visibility.Collapsed
                 BtnUpdate.Visibility = Visibility.Collapsed
             End If
             End If
-            '标题
-            If Entry.Comp Is Nothing Then
-                Title = Entry.Name
-                SubTitle = If(Entry.Version Is Nothing, "", "  |  " & Entry.Version)
-            Else
-                Dim Titles = Entry.Comp.GetControlTitle(False)
-                Title = Titles.Key
-                SubTitle = Titles.Value & If(Entry.Version Is Nothing, "", "  |  " & Entry.Version)
-            End If
-            If Checked Then
-                LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush2", "ColorBrush5"))
-            Else
-                LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush1", "ColorBrushGray4"))
-            End If
-            '描述
-            Dim NewDescription As String
+            '标题与描述
+            Dim DescFileName As String
             Select Case Entry.State
             Select Case Entry.State
                 Case McMod.McModState.Fine
                 Case McMod.McModState.Fine
-                    NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path)
+                    DescFileName = GetFileNameWithoutExtentionFromPath(Entry.Path)
                 Case McMod.McModState.Disabled
                 Case McMod.McModState.Disabled
-                    NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path.Replace(".disabled", "").Replace(".old", ""))
+                    DescFileName = GetFileNameWithoutExtentionFromPath(Entry.Path.Replace(".disabled", "").Replace(".old", ""))
                 Case Else 'McMod.McModState.Unavailable
                 Case Else 'McMod.McModState.Unavailable
-                    NewDescription = GetFileNameFromPath(Entry.Path)
+                    DescFileName = GetFileNameFromPath(Entry.Path)
             End Select
             End Select
+            Dim NewDescription As String
+            If Setup.Get("ToolModLocalNameStyle") = 1 Then
+                '标题显示文件名,详情显示译名
+                '标题
+                Title = DescFileName
+                SubTitle = ""
+                '描述
+                If Entry.Comp Is Nothing Then
+                    NewDescription = Entry.Name
+                Else
+                    Dim Titles = Entry.Comp.GetControlTitle(False)
+                    NewDescription = Titles.Key & Titles.Value
+                End If
+                NewDescription = NewDescription.Replace("  |  ", " / ")
+                If Entry.Version IsNot Nothing Then NewDescription &= $" ({Entry.Version})"
+            Else
+                '标题显示译名,详情显示文件名
+                '标题
+                If Entry.Comp Is Nothing Then
+                    Title = Entry.Name
+                    SubTitle = If(Entry.Version Is Nothing, "", "  |  " & Entry.Version)
+                Else
+                    Dim Titles = Entry.Comp.GetControlTitle(False)
+                    Title = Titles.Key
+                    SubTitle = Titles.Value & If(Entry.Version Is Nothing, "", "  |  " & Entry.Version)
+                End If
+                '描述
+                NewDescription = DescFileName
+            End If
             If Entry.Comp IsNot Nothing Then
             If Entry.Comp IsNot Nothing Then
                 NewDescription += ": " & Entry.Comp.Description.Replace(vbCr, "").Replace(vbLf, "")
                 NewDescription += ": " & Entry.Comp.Description.Replace(vbCr, "").Replace(vbLf, "")
             ElseIf Entry.Description IsNot Nothing Then
             ElseIf Entry.Description IsNot Nothing Then
@@ -436,6 +377,11 @@ RetryStart:
                 NewDescription += ": " & "存在错误,无法获取信息"
                 NewDescription += ": " & "存在错误,无法获取信息"
             End If
             End If
             Description = NewDescription
             Description = NewDescription
+            If Checked Then
+                LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush2", "ColorBrush5"))
+            Else
+                LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush1", "ColorBrushGray4"))
+            End If
             '主 Logo
             '主 Logo
             Logo = If(Entry.Comp Is Nothing, PathImage & "Icons/NoIcon.png", Entry.Comp.GetControlLogo())
             Logo = If(Entry.Comp Is Nothing, PathImage & "Icons/NoIcon.png", Entry.Comp.GetControlLogo())
             '图标右下角的 Logo
             '图标右下角的 Logo

+ 12 - 1
Plain Craft Launcher 2/Modules/ModEvent.vb

@@ -92,7 +92,18 @@
                         MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的下载协议。", "事件执行失败")
                         MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的下载协议。", "事件执行失败")
                         Exit Sub
                         Exit Sub
                     End If
                     End If
-                    PageOtherTest.StartCustomDownload(Data(0), GetFileNameFromPath(Data(0)))
+                    Try
+                        Select Case Data.Length
+                            Case 1
+                                PageOtherTest.StartCustomDownload(Data(0), GetFileNameFromPath(Data(0)))
+                            Case 2
+                                PageOtherTest.StartCustomDownload(Data(0), Data(1))
+                            Case Else
+                                PageOtherTest.StartCustomDownload(Data(0), Data(1), Data(2))
+                        End Select
+                    Catch
+                        PageOtherTest.StartCustomDownload(Data(0), "未知")
+                    End Try
 
 
                 Case Else
                 Case Else
                     MyMsgBox("未知的事件类型:" & Type & vbCrLf & "请检查事件类型填写是否正确,或者 PCL 是否为最新版本。", "事件执行失败")
                     MyMsgBox("未知的事件类型:" & Type & vbCrLf & "请检查事件类型填写是否正确,或者 PCL 是否为最新版本。", "事件执行失败")

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

@@ -101,8 +101,8 @@ Friend Module ModSecret
     ''' 设置 Headers 的 UA、Referer。
     ''' 设置 Headers 的 UA、Referer。
     ''' </summary>
     ''' </summary>
     Friend Sub SecretHeadersSign(Url As String, ByRef Client As WebClient, Optional UseBrowserUserAgent As Boolean = False)
     Friend Sub SecretHeadersSign(Url As String, ByRef Client As WebClient, Optional UseBrowserUserAgent As Boolean = False)
-        If Url.Contains("modrinth.com") Then '根据 #4334,不添加 PCL 的 UA 反而能正常访问
-            Client.Headers("User-Agent") = "Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
+        If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then
+            Client.Headers("User-Agent") = "LogStatistic" '#4951
         ElseIf UseBrowserUserAgent Then
         ElseIf UseBrowserUserAgent Then
             Client.Headers("User-Agent") = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
             Client.Headers("User-Agent") = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
         Else
         Else
@@ -115,8 +115,8 @@ Friend Module ModSecret
     ''' 设置 Headers 的 UA、Referer。
     ''' 设置 Headers 的 UA、Referer。
     ''' </summary>
     ''' </summary>
     Friend Sub SecretHeadersSign(Url As String, ByRef Request As HttpWebRequest, Optional UseBrowserUserAgent As Boolean = False)
     Friend Sub SecretHeadersSign(Url As String, ByRef Request As HttpWebRequest, Optional UseBrowserUserAgent As Boolean = False)
-        If Url.Contains("modrinth.com") Then '根据 #4334,不添加 PCL 的 UA 反而能正常访问
-            Request.UserAgent = "Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
+        If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then
+            Client.Headers("User-Agent") = "LogStatistic" '#4951
         ElseIf UseBrowserUserAgent Then
         ElseIf UseBrowserUserAgent Then
             Request.UserAgent = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
             Request.UserAgent = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
         Else
         Else

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

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

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

@@ -1,4 +1,5 @@
 Imports System.IO.Compression
 Imports System.IO.Compression
+Imports System.Net.Http
 
 
 Public Module ModDownloadLib
 Public Module ModDownloadLib
 
 
@@ -59,47 +60,6 @@ Public Module ModDownloadLib
             Return Nothing
             Return Nothing
         End Try
         End Try
     End Function
     End Function
-    ''' <summary>
-    ''' 保存某个 Minecraft 版本的核心文件(仅 Json 与核心 Jar)。
-    ''' </summary>
-    ''' <param name="Id">所下载的 Minecraft 的版本名。</param>
-    ''' <param name="JsonUrl">Json 文件的 Mojang 官方地址。</param>
-    Public Sub McDownloadClientCore(Id As String, JsonUrl As String, Behaviour As NetPreDownloadBehaviour)
-        Try
-            Dim VersionFolder As String = SelectFolder()
-            If Not VersionFolder.Contains("\") Then Exit Sub
-            VersionFolder = VersionFolder & Id & "\"
-
-            '重复任务检查
-            For Each OngoingLoader In LoaderTaskbar.ToList()
-                If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For
-                If Behaviour = NetPreDownloadBehaviour.ExitWhileExistsOrDownloading Then Exit Sub
-                Hint("该版本正在下载中!", HintType.Critical)
-                Exit Sub
-            Next
-
-            Dim Loaders As New List(Of LoaderBase)
-            '下载版本 Json 文件
-            Loaders.Add(New LoaderDownload("下载版本 json 文件", New List(Of NetFile) From {
-                New NetFile(DlSourceLauncherOrMetaGet(JsonUrl), VersionFolder & Id & ".json", New FileChecker(CanUseExistsFile:=False, IsJson:=True))
-            }) With {.ProgressWeight = 2})
-            '获取支持库文件地址
-            Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析核心 jar 文件下载地址",
-                                                            Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionFolder), True)) With {.ProgressWeight = 0.5, .Show = False})
-            '下载支持库文件
-            Loaders.Add(New LoaderDownload("下载核心 jar 文件", New List(Of NetFile)) With {.ProgressWeight = 5})
-
-            '启动
-            Dim Loader As New LoaderCombo(Of String)("Minecraft " & Id & " 下载", Loaders) With {.OnStateChanged = AddressOf DownloadStateSave}
-            Loader.Start(Id)
-            LoaderTaskbarAdd(Loader)
-            FrmMain.BtnExtraDownload.ShowRefresh()
-            FrmMain.BtnExtraDownload.Ribble()
-
-        Catch ex As Exception
-            Log(ex, "开始 Minecraft 下载失败", LogLevel.Feedback)
-        End Try
-    End Sub
 
 
     ''' <summary>
     ''' <summary>
     ''' 获取下载某个 Minecraft 版本的加载器列表。
     ''' 获取下载某个 Minecraft 版本的加载器列表。
@@ -207,19 +167,14 @@ Public Module ModDownloadLib
         ToolTipService.SetVerticalOffset(BtnInfo, 30)
         ToolTipService.SetVerticalOffset(BtnInfo, 30)
         ToolTipService.SetHorizontalOffset(BtnInfo, 2)
         ToolTipService.SetHorizontalOffset(BtnInfo, 2)
         AddHandler BtnInfo.Click, AddressOf McDownloadMenuLog
         AddHandler BtnInfo.Click, AddressOf McDownloadMenuLog
-        Dim BtnServer As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonServer, .ToolTip = "下载服务端"}
+        Dim BtnServer As New MyIconButton With {.LogoScale = 1, .Logo = Logo.IconButtonServer, .ToolTip = "下载服务端"}
         ToolTipService.SetPlacement(BtnServer, Primitives.PlacementMode.Center)
         ToolTipService.SetPlacement(BtnServer, Primitives.PlacementMode.Center)
         ToolTipService.SetVerticalOffset(BtnServer, 30)
         ToolTipService.SetVerticalOffset(BtnServer, 30)
         ToolTipService.SetHorizontalOffset(BtnServer, 2)
         ToolTipService.SetHorizontalOffset(BtnServer, 2)
-        AddHandler BtnServer.Click, AddressOf McDownloadMenuServer
+        AddHandler BtnServer.Click, AddressOf McDownloadMenuSaveServer
         sender.Buttons = {BtnServer, BtnInfo}
         sender.Buttons = {BtnServer, BtnInfo}
     End Sub
     End Sub
     Private Sub McDownloadMenuBuild(sender As Object, e As EventArgs)
     Private Sub McDownloadMenuBuild(sender As Object, e As EventArgs)
-        Dim BtnServer As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonServer, .ToolTip = "下载服务端"}
-        ToolTipService.SetPlacement(BtnServer, Primitives.PlacementMode.Center)
-        ToolTipService.SetVerticalOffset(BtnServer, 30)
-        ToolTipService.SetHorizontalOffset(BtnServer, 2)
-        AddHandler BtnServer.Click, AddressOf McDownloadMenuServer
         Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"}
         Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"}
         ToolTipService.SetPlacement(BtnSave, Primitives.PlacementMode.Center)
         ToolTipService.SetPlacement(BtnSave, Primitives.PlacementMode.Center)
         ToolTipService.SetVerticalOffset(BtnSave, 30)
         ToolTipService.SetVerticalOffset(BtnSave, 30)
@@ -230,7 +185,12 @@ Public Module ModDownloadLib
         ToolTipService.SetVerticalOffset(BtnInfo, 30)
         ToolTipService.SetVerticalOffset(BtnInfo, 30)
         ToolTipService.SetHorizontalOffset(BtnInfo, 2)
         ToolTipService.SetHorizontalOffset(BtnInfo, 2)
         AddHandler BtnInfo.Click, AddressOf McDownloadMenuLog
         AddHandler BtnInfo.Click, AddressOf McDownloadMenuLog
-        sender.Buttons = {BtnServer, BtnSave, BtnInfo}
+        Dim BtnServer As New MyIconButton With {.LogoScale = 1, .Logo = Logo.IconButtonServer, .ToolTip = "下载服务端"}
+        ToolTipService.SetPlacement(BtnServer, Primitives.PlacementMode.Center)
+        ToolTipService.SetVerticalOffset(BtnServer, 30)
+        ToolTipService.SetHorizontalOffset(BtnServer, 2)
+        AddHandler BtnServer.Click, AddressOf McDownloadMenuSaveServer
+        sender.Buttons = {BtnSave, BtnInfo, BtnServer}
     End Sub
     End Sub
     Private Sub McDownloadMenuLog(sender As Object, e As RoutedEventArgs)
     Private Sub McDownloadMenuLog(sender As Object, e As RoutedEventArgs)
         Dim Version As JToken
         Dim Version As JToken
@@ -243,60 +203,74 @@ Public Module ModDownloadLib
         End If
         End If
         McUpdateLogShow(Version)
         McUpdateLogShow(Version)
     End Sub
     End Sub
-    Private Sub McDownloadMenuServer(sender As Object, e As RoutedEventArgs)
-        Dim Version As JToken
-        If sender.Tag IsNot Nothing Then
-            Version = sender.Tag
-        ElseIf sender.Parent.Tag IsNot Nothing Then
-            Version = sender.Parent.Tag
+    Private Sub McDownloadMenuSaveServer(sender As Object, e As RoutedEventArgs)
+        Dim Version As MyListItem
+        If TypeOf sender Is MyListItem Then
+            Version = sender
+        ElseIf TypeOf sender.Parent Is MyListItem Then
+            Version = sender.Parent
         Else
         Else
-            Version = sender.Parent.Parent.Tag
+            Version = sender.Parent.Parent
         End If
         End If
-        RunInThread(Sub()
-                        Dim Json As JObject
-                        Try
-                            Json = NetGetCodeByRequestRetry(Version("url"), IsJson:=True)
-                        Catch ex As Exception
-                            Log(ex, "未找能获取到服务端文件,文件可能不存在")
-                        End Try
-                        If Json?("downloads")?("server") Is Nothing Then
-                            Hint("此版本暂未有服务端文件提供下载")
-                        Else
-                            RunInUi(Sub()
-                                        Try
-                                            Dim Url As String = Json("downloads")("server")("url").ToString()
-                                            Dim FileName As String = Version("id").ToString() & "-server.jar"
-                                            Dim VersionID As String = Version("id").ToString()
-                                            Dim Target As String = SelectAs("选择保存位置", FileName, "原版服务端 (*.jar)|*.jar")
-                                            If Not Target.Contains("\") Then Exit Sub
-
-                                            '重复任务检查
-                                            For Each OngoingLoader In LoaderTaskbar
-                                                If OngoingLoader.Name <> $"{VersionID} 服务端下载" Then Continue For
-                                                Hint("该服务端文件正在下载中!", HintType.Critical)
-                                                Exit Sub
-                                            Next
-
-                                            '构造步骤加载器
-                                            Dim Loaders As New List(Of LoaderBase)
-                                            '下载
-                                            Dim Address As New List(Of String)
-                                            Address.Add(Url)
-                                            Loaders.Add(New LoaderDownload("下载服务端文件", New List(Of NetFile) From {New NetFile(Address.ToArray, Target, New FileChecker(MinSize:=1024 * 64))}) With {.ProgressWeight = 15})
-                                            '启动
-                                            Dim Loader As New LoaderCombo(Of JObject)($"{VersionID} 服务端下载", Loaders) With {.OnStateChanged = AddressOf DownloadStateSave}
-                                            Loader.Start(Json("downloads")("server"))
-                                            LoaderTaskbarAdd(Loader)
-                                            FrmMain.BtnExtraDownload.ShowRefresh()
-                                            FrmMain.BtnExtraDownload.Ribble()
-
-                                        Catch ex As Exception
-                                            Log(ex, "开始服务端文件下载失败", LogLevel.Feedback)
-                                        End Try
-                                    End Sub)
-                        End If
-                    End Sub
-            )
+        Try
+            Dim Id = Version.Title
+            Dim JsonUrl = Version.Tag("url").ToString
+            Dim VersionFolder As String = SelectFolder()
+            If Not VersionFolder.Contains("\") Then Exit Sub
+            VersionFolder = VersionFolder & Id & "\"
+
+            '重复任务检查
+            For Each OngoingLoader In LoaderTaskbar.ToList()
+                If OngoingLoader.Name <> $"Minecraft {Id} 服务端下载" Then Continue For
+                Hint("该服务端正在下载中!", HintType.Critical)
+                Exit Sub
+            Next
+
+            Dim Loaders As New List(Of LoaderBase)
+            '下载版本 JSON 文件
+            Loaders.Add(New LoaderDownload("下载版本 JSON 文件", New List(Of NetFile) From {
+                New NetFile(DlSourceLauncherOrMetaGet(JsonUrl), VersionFolder & Id & ".json", New FileChecker(CanUseExistsFile:=False, IsJson:=True))
+            }) With {.ProgressWeight = 2})
+            '构建服务端
+            Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("构建服务端",
+                Sub(Task As LoaderTask(Of String, List(Of NetFile)))
+                    '分析服务端 JAR 文件下载地址
+                    Dim McVersion As New McVersion(VersionFolder)
+                    If McVersion.JsonObject("downloads") Is Nothing OrElse McVersion.JsonObject("downloads")("server") Is Nothing OrElse McVersion.JsonObject("downloads")("server")("url") Is Nothing Then Throw New Exception($"{Id} 版本没有提供服务端文件")
+                    Dim JarUrl As String = McVersion.JsonObject("downloads")("server")("url")
+                    Dim Checker As New FileChecker(MinSize:=1024, ActualSize:=If(McVersion.JsonObject("downloads")("server")("size"), -1), Hash:=McVersion.JsonObject("downloads")("server")("sha1"))
+                    Task.Output = New List(Of NetFile) From {New NetFile(DlSourceLauncherOrMetaGet(JarUrl), VersionFolder & Id & "-server.jar", Checker)}
+                    '添加启动脚本
+                    Dim Bat As String =
+$"@echo off
+title {Id} 原版服务端
+echo 如果服务端立即停止,请右键编辑该脚本,将下一行开头的 java 替换为适合该 Minecraft 版本的完整 java.exe 的路径。
+echo 你可以在 PCL 的 [设置 → 启动选项] 中查看已安装的 java,所需的 java.exe 一般在其中的 bin 文件夹下。
+echo ------------------------------
+echo 如果提示 ""You need to agree to the EULA in order to run the server"",请打开 eula.txt,按说明阅读并同意 Minecraft EULA 后,将该文件最后一行中的 eula=false 改为 eula=true。
+echo ------------------------------
+""java"" -server -XX:+UseG1GC -Xmx4096M -Xms1024M -XX:+UseCompressedOops -jar {Id}-server.jar nogui
+echo ----------------------
+echo 服务端已停止。
+pause"
+                    WriteFile(VersionFolder & "Launch Server.bat", Bat,
+                        Encoding:=If(Encoding.Default.Equals(Encoding.UTF8), Encoding.UTF8, Encoding.GetEncoding("GB18030")))
+                    '删除版本 JSON
+                    File.Delete(VersionFolder & Id & ".json")
+                End Sub
+            ) With {.ProgressWeight = 0.5, .Show = False})
+            '下载服务端文件
+            Loaders.Add(New LoaderDownload("下载服务端文件", New List(Of NetFile)) With {.ProgressWeight = 5})
+
+            '启动
+            Dim Loader As New LoaderCombo(Of String)("Minecraft " & Id & " 服务端下载", Loaders) With {.OnStateChanged = AddressOf DownloadStateSave}
+            Loader.Start(Id)
+            LoaderTaskbarAdd(Loader)
+            FrmMain.BtnExtraDownload.ShowRefresh()
+            FrmMain.BtnExtraDownload.Ribble()
+        Catch ex As Exception
+            Log(ex, "开始 Minecraft 服务端下载失败", LogLevel.Feedback)
+        End Try
     End Sub
     End Sub
     Public Sub McDownloadMenuSave(sender As Object, e As RoutedEventArgs)
     Public Sub McDownloadMenuSave(sender As Object, e As RoutedEventArgs)
         Dim Version As MyListItem
         Dim Version As MyListItem
@@ -307,7 +281,41 @@ Public Module ModDownloadLib
         Else
         Else
             Version = sender.Parent.Parent
             Version = sender.Parent.Parent
         End If
         End If
-        McDownloadClientCore(Version.Title, Version.Tag("url").ToString, NetPreDownloadBehaviour.HintWhileExists)
+        Try
+            Dim Id = Version.Title
+            Dim JsonUrl = Version.Tag("url").ToString
+            Dim VersionFolder As String = SelectFolder()
+            If Not VersionFolder.Contains("\") Then Exit Sub
+            VersionFolder = VersionFolder & Id & "\"
+
+            '重复任务检查
+            For Each OngoingLoader In LoaderTaskbar.ToList()
+                If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For
+                Hint("该版本正在下载中!", HintType.Critical)
+                Exit Sub
+            Next
+
+            Dim Loaders As New List(Of LoaderBase)
+            '下载版本 JSON 文件
+            Loaders.Add(New LoaderDownload("下载版本 JSON 文件", New List(Of NetFile) From {
+                New NetFile(DlSourceLauncherOrMetaGet(JsonUrl), VersionFolder & Id & ".json", New FileChecker(CanUseExistsFile:=False, IsJson:=True))
+            }) With {.ProgressWeight = 2})
+            '获取支持库文件地址
+            Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析核心 JAR 文件下载地址",
+                Sub(Task) Task.Output = New List(Of NetFile) From {DlClientJarGet(New McVersion(VersionFolder), False)}
+            ) With {.ProgressWeight = 0.5, .Show = False})
+            '下载支持库文件
+            Loaders.Add(New LoaderDownload("下载核心 JAR 文件", New List(Of NetFile)) With {.ProgressWeight = 5})
+
+            '启动
+            Dim Loader As New LoaderCombo(Of String)("Minecraft " & Id & " 下载", Loaders) With {.OnStateChanged = AddressOf DownloadStateSave}
+            Loader.Start(Id)
+            LoaderTaskbarAdd(Loader)
+            FrmMain.BtnExtraDownload.ShowRefresh()
+            FrmMain.BtnExtraDownload.Ribble()
+        Catch ex As Exception
+            Log(ex, "开始 Minecraft 下载失败", LogLevel.Feedback)
+        End Try
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
     ''' 显示某 Minecraft 版本的更新日志。
     ''' 显示某 Minecraft 版本的更新日志。
@@ -424,7 +432,7 @@ Public Module ModDownloadLib
         '添加 Java Wrapper 作为主 Jar
         '添加 Java Wrapper 作为主 Jar
         Dim Arguments As String
         Dim Arguments As String
         If UseJavaWrapper Then
         If UseJavaWrapper Then
-            Arguments = $"-Doolloo.jlw.tmpdir=""{GetPureASCIIDir()}"" -Duser.home=""{BaseMcFolderHome}"" -cp ""{Target}"" -jar ""{ExtractJavaWrapper()}"" optifine.Installer"
+            Arguments = $"-Doolloo.jlw.tmpdir=""{PathPure.TrimEnd("\")}"" -Duser.home=""{BaseMcFolderHome}"" -cp ""{Target}"" -jar ""{ExtractJavaWrapper()}"" optifine.Installer"
         Else
         Else
             Arguments = $"-Duser.home=""{BaseMcFolderHome}"" -cp ""{Target}"" optifine.Installer"
             Arguments = $"-Duser.home=""{BaseMcFolderHome}"" -cp ""{Target}"" optifine.Installer"
         End If
         End If
@@ -1091,7 +1099,7 @@ Retry:
         '添加 Java Wrapper 作为主 Jar
         '添加 Java Wrapper 作为主 Jar
         Dim Arguments As String
         Dim Arguments As String
         If UseJavaWrapper Then
         If UseJavaWrapper Then
-            Arguments = $"-Doolloo.jlw.tmpdir=""{GetPureASCIIDir()}"" -cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" -jar ""{ExtractJavaWrapper()}"" com.bangbang93.ForgeInstaller ""{McFolder}"
+            Arguments = $"-Doolloo.jlw.tmpdir=""{PathPure.TrimEnd("\")}"" -cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" -jar ""{ExtractJavaWrapper()}"" com.bangbang93.ForgeInstaller ""{McFolder}"
         Else
         Else
             Arguments = $"-cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" com.bangbang93.ForgeInstaller ""{McFolder}"
             Arguments = $"-cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" com.bangbang93.ForgeInstaller ""{McFolder}"
         End If
         End If
@@ -1446,8 +1454,6 @@ Retry:
                         '没有新增文件夹
                         '没有新增文件夹
                         Log("[Download] 未找到新增的版本文件夹")
                         Log("[Download] 未找到新增的版本文件夹")
                     End If
                     End If
-                    '新建 mods 文件夹
-                    Directory.CreateDirectory(New McVersion(VersionFolder).GetPathIndie(True) & "mods\")
                 Catch ex As Exception
                 Catch ex As Exception
                     Throw New Exception($"安装新 {LoaderName} 版本失败", ex)
                     Throw New Exception($"安装新 {LoaderName} 版本失败", ex)
                 Finally
                 Finally
@@ -1463,57 +1469,55 @@ Retry:
         Else
         Else
             Log("[Download] 检测为非新版 Forge:" & LoaderVersion)
             Log("[Download] 检测为非新版 Forge:" & LoaderVersion)
             Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)($"安装 {LoaderName}(方式 B)",
             Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)($"安装 {LoaderName}(方式 B)",
-Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
-    Dim Installer As ZipArchive = Nothing
-    Try
-        '解压并获取信息
-        Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open))
-        Task.Progress = 0.2
-        Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open))
-        Task.Progress = 0.4
-        '新建版本文件夹
-        Directory.CreateDirectory(VersionFolder)
-        Task.Progress = 0.5
-        If Json("install") Is Nothing Then
-            '中版:Legacy 方式 1
-            Log("[Download] 开始进行 Forge 安装,Legacy 方式 1:" & InstallerAddress)
-            '建立 Json 文件
-            Dim JsonVersion As JObject = GetJson(ReadFile(Installer.GetEntry(Json("json").ToString.TrimStart("/")).Open))
-            JsonVersion("id") = TargetVersion
-            WriteFile(VersionFolder & TargetVersion & ".json", JsonVersion.ToString)
-            Task.Progress = 0.6
-            '解压支持库文件
-            Installer.Dispose()
-            ExtractFile(InstallerAddress, InstallerAddress & "_unrar\")
-            CopyDirectory(InstallerAddress & "_unrar\maven\", McFolder & "libraries\")
-            DeleteDirectory(InstallerAddress & "_unrar\")
-        Else
-            '旧版:Legacy 方式 2
-            Log("[Download] 开始进行 Forge 安装,Legacy 方式 2:" & InstallerAddress)
-            '解压 Jar 文件
-            Dim JarAddress As String = McLibGet(Json("install")("path"), CustomMcFolder:=McFolder)
-            If File.Exists(JarAddress) Then File.Delete(JarAddress)
-            WriteFile(JarAddress, Installer.GetEntry(Json("install")("filePath")).Open)
-            Task.Progress = 0.9
-            '建立 Json 文件
-            Json("versionInfo")("id") = TargetVersion
-            If Json("versionInfo")("inheritsFrom") Is Nothing Then CType(Json("versionInfo"), JObject).Add("inheritsFrom", Inherit)
-            WriteFile(VersionFolder & TargetVersion & ".json", Json("versionInfo").ToString)
-        End If
-        '新建 mods 文件夹
-        Directory.CreateDirectory(New McVersion(VersionFolder).GetPathIndie(True) & "mods\")
-    Catch ex As Exception
-        Throw New Exception("非新版方式安装 Forge 失败", ex)
-    Finally
-        Try
-            '清理文件
-            If Installer IsNot Nothing Then Installer.Dispose()
-            If File.Exists(InstallerAddress) Then File.Delete(InstallerAddress)
-            If Directory.Exists(InstallerAddress & "_unrar\") Then DeleteDirectory(InstallerAddress & "_unrar\")
-        Catch ex As Exception
-            Log(ex, "非新版方式安装 Forge 清理文件时出错")
-        End Try
-    End Try
+            Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
+                Dim Installer As ZipArchive = Nothing
+                Try
+                    '解压并获取信息
+                    Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open))
+                    Task.Progress = 0.2
+                    Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open))
+                    Task.Progress = 0.4
+                    '新建版本文件夹
+                    Directory.CreateDirectory(VersionFolder)
+                    Task.Progress = 0.5
+                    If Json("install") Is Nothing Then
+                        '中版:Legacy 方式 1
+                        Log("[Download] 开始进行 Forge 安装,Legacy 方式 1:" & InstallerAddress)
+                        '建立 Json 文件
+                        Dim JsonVersion As JObject = GetJson(ReadFile(Installer.GetEntry(Json("json").ToString.TrimStart("/")).Open))
+                        JsonVersion("id") = TargetVersion
+                        WriteFile(VersionFolder & TargetVersion & ".json", JsonVersion.ToString)
+                        Task.Progress = 0.6
+                        '解压支持库文件
+                        Installer.Dispose()
+                        ExtractFile(InstallerAddress, InstallerAddress & "_unrar\")
+                        CopyDirectory(InstallerAddress & "_unrar\maven\", McFolder & "libraries\")
+                        DeleteDirectory(InstallerAddress & "_unrar\")
+                    Else
+                        '旧版:Legacy 方式 2
+                        Log("[Download] 开始进行 Forge 安装,Legacy 方式 2:" & InstallerAddress)
+                        '解压 Jar 文件
+                        Dim JarAddress As String = McLibGet(Json("install")("path"), CustomMcFolder:=McFolder)
+                        If File.Exists(JarAddress) Then File.Delete(JarAddress)
+                        WriteFile(JarAddress, Installer.GetEntry(Json("install")("filePath")).Open)
+                        Task.Progress = 0.9
+                        '建立 Json 文件
+                        Json("versionInfo")("id") = TargetVersion
+                        If Json("versionInfo")("inheritsFrom") Is Nothing Then CType(Json("versionInfo"), JObject).Add("inheritsFrom", Inherit)
+                        WriteFile(VersionFolder & TargetVersion & ".json", Json("versionInfo").ToString)
+                    End If
+                Catch ex As Exception
+                    Throw New Exception("非新版方式安装 Forge 失败", ex)
+                Finally
+                    Try
+                        '清理文件
+                        If Installer IsNot Nothing Then Installer.Dispose()
+                        If File.Exists(InstallerAddress) Then File.Delete(InstallerAddress)
+                        If Directory.Exists(InstallerAddress & "_unrar\") Then DeleteDirectory(InstallerAddress & "_unrar\")
+                    Catch ex As Exception
+                        Log(ex, "非新版方式安装 Forge 清理文件时出错")
+                    End Try
+                End Try
             End Sub) With {.ProgressWeight = 1})
             End Sub) With {.ProgressWeight = 1})
         End If
         End If
 
 
@@ -1830,8 +1834,6 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
                 "https://bmclapi2.bangbang93.com/fabric-meta/v2/versions/loader/" & MinecraftName & "/" & FabricVersion & "/profile/json",
                 "https://bmclapi2.bangbang93.com/fabric-meta/v2/versions/loader/" & MinecraftName & "/" & FabricVersion & "/profile/json",
                 "https://meta.fabricmc.net/v2/versions/loader/" & MinecraftName & "/" & FabricVersion & "/profile/json"
                 "https://meta.fabricmc.net/v2/versions/loader/" & MinecraftName & "/" & FabricVersion & "/profile/json"
             }, VersionFolder & Id & ".json", New FileChecker(IsJson:=True))}
             }, VersionFolder & Id & ".json", New FileChecker(IsJson:=True))}
-            '新建 mods 文件夹
-            Directory.CreateDirectory(New McVersion(VersionFolder).GetPathIndie(True) & "mods\")
         End Sub) With {.ProgressWeight = 0.5})
         End Sub) With {.ProgressWeight = 0.5})
         Loaders.Add(New LoaderDownload("下载 Fabric 主文件", New List(Of NetFile)) With {.ProgressWeight = 2.5})
         Loaders.Add(New LoaderDownload("下载 Fabric 主文件", New List(Of NetFile)) With {.ProgressWeight = 2.5})
 
 
@@ -2090,9 +2092,11 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
         Dim OptiFineAsMod As Boolean = Request.OptiFineEntry IsNot Nothing AndAlso '1. 选择了 OptiFine
         Dim OptiFineAsMod As Boolean = Request.OptiFineEntry IsNot Nothing AndAlso '1. 选择了 OptiFine
             (Request.FabricVersion IsNot Nothing OrElse '2. 选择了 Fabric...
             (Request.FabricVersion IsNot Nothing OrElse '2. 选择了 Fabric...
             (Request.ForgeEntry IsNot Nothing AndAlso MinecraftCode >= 14 AndAlso MinecraftCode <= 15)) '...或者 Forge 1.14~15(#4134)
             (Request.ForgeEntry IsNot Nothing AndAlso MinecraftCode >= 14 AndAlso MinecraftCode <= 15)) '...或者 Forge 1.14~15(#4134)
+        Dim ModsFolder As String = New McVersion(OutputFolder).GetPathIndie(True) & "mods\"
+
         If OptiFineAsMod Then
         If OptiFineAsMod Then
             Log("[Download] OptiFine 将作为 Mod 进行下载")
             Log("[Download] OptiFine 将作为 Mod 进行下载")
-            OptiFineFolder = New McVersion(OutputFolder).GetPathIndie(True) & "mods\"
+            OptiFineFolder = ModsFolder
         End If
         End If
 
 
         '记录日志
         '记录日志
@@ -2114,11 +2118,11 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
         LoaderList.Add(New LoaderTask(Of Integer, Integer)("添加忽略标识", Sub() WriteFile(OutputFolder & ".pclignore", "用于临时地在 PCL 的版本列表中屏蔽此版本。")) With {.Show = False, .Block = False})
         LoaderList.Add(New LoaderTask(Of Integer, Integer)("添加忽略标识", Sub() WriteFile(OutputFolder & ".pclignore", "用于临时地在 PCL 的版本列表中屏蔽此版本。")) With {.Show = False, .Block = False})
         'Fabric API
         'Fabric API
         If Request.FabricApi IsNot Nothing Then
         If Request.FabricApi IsNot Nothing Then
-            LoaderList.Add(New LoaderDownload("下载 Fabric API", New List(Of NetFile) From {Request.FabricApi.ToNetFile(New McVersion(OutputFolder).GetPathIndie(True) & "mods\")}) With {.ProgressWeight = 3, .Block = False})
+            LoaderList.Add(New LoaderDownload("下载 Fabric API", New List(Of NetFile) From {Request.FabricApi.ToNetFile(ModsFolder)}) With {.ProgressWeight = 3, .Block = False})
         End If
         End If
         'OptiFabric
         'OptiFabric
         If Request.OptiFabric IsNot Nothing Then
         If Request.OptiFabric IsNot Nothing Then
-            LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {Request.OptiFabric.ToNetFile(New McVersion(OutputFolder).GetPathIndie(True) & "mods\")}) With {.ProgressWeight = 3, .Block = False})
+            LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {Request.OptiFabric.ToNetFile(ModsFolder)}) With {.ProgressWeight = 3, .Block = False})
         End If
         End If
         '原版
         '原版
         Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.MinecraftName, McDownloadClientLoader(Request.MinecraftName, Request.MinecraftJson, Request.TargetVersionName)) With {.Show = False, .ProgressWeight = 39, .Block = Request.ForgeVersion Is Nothing AndAlso Request.OptiFineEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}
         Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.MinecraftName, McDownloadClientLoader(Request.MinecraftName, Request.MinecraftJson, Request.TargetVersionName)) With {.Show = False, .ProgressWeight = 39, .Block = Request.ForgeVersion Is Nothing AndAlso Request.OptiFineEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}
@@ -2153,7 +2157,12 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
                 InstallMerge(OutputFolder, OutputFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, NeoForgeFolder, Request.NeoForgeVersion, FabricFolder, LiteLoaderFolder)
                 InstallMerge(OutputFolder, OutputFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, NeoForgeFolder, Request.NeoForgeVersion, FabricFolder, LiteLoaderFolder)
                 Task.Progress = 0.3
                 Task.Progress = 0.3
                 If Directory.Exists(TempMcFolder & "libraries") Then CopyDirectory(TempMcFolder & "libraries", PathMcFolder & "libraries")
                 If Directory.Exists(TempMcFolder & "libraries") Then CopyDirectory(TempMcFolder & "libraries", PathMcFolder & "libraries")
-                If Directory.Exists(TempMcFolder & "mods") Then CopyDirectory(TempMcFolder & "mods", PathMcFolder & "mods")
+                If Directory.Exists(TempMcFolder & "mods") Then CopyDirectory(TempMcFolder & "mods", ModsFolder)
+                '新建 mods 文件夹
+                If Request.ForgeVersion IsNot Nothing OrElse Request.FabricVersion IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing Then
+                    Directory.CreateDirectory(ModsFolder)
+                    Log("[Download] 自动创建 mods 文件夹:" & ModsFolder)
+                End If
             End Sub) With {.ProgressWeight = 2, .Block = True})
             End Sub) With {.ProgressWeight = 2, .Block = True})
         '补全文件
         '补全文件
         If Not DontFixLibraries AndAlso
         If Not DontFixLibraries AndAlso

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

@@ -167,7 +167,7 @@
             '构造步骤加载器
             '构造步骤加载器
             Dim Loaders As New List(Of LoaderBase)
             Dim Loaders As New List(Of LoaderBase)
             Dim Target As String = $"{PathMcFolder}versions\{VersionName}\原始整合包.{If(Project.FromCurseForge, "zip", "mrpack")}"
             Dim Target As String = $"{PathMcFolder}versions\{VersionName}\原始整合包.{If(Project.FromCurseForge, "zip", "mrpack")}"
-            Dim LogoFileAddress As String = PathTemp & "CompLogo\" & GetHash(CompItem.Logo) & ".png"
+            Dim LogoFileAddress As String = MyImage.GetTempPath(CompItem.Logo)
             Loaders.Add(New LoaderDownload("下载整合包文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 10, .Block = True})
             Loaders.Add(New LoaderDownload("下载整合包文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 10, .Block = True})
             Loaders.Add(New LoaderTask(Of Integer, Integer)("准备安装整合包",
             Loaders.Add(New LoaderTask(Of Integer, Integer)("准备安装整合包",
             Sub()
             Sub()

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

@@ -61,7 +61,7 @@
             PageChangeRun(PageGet(ID))
             PageChangeRun(PageGet(ID))
             PageID = ID
             PageID = ID
         Catch ex As Exception
         Catch ex As Exception
-            Log(ex, "切换设置分页面失败(ID " & ID & ")", LogLevel.Feedback)
+            Log(ex, "切换分页面失败(ID " & ID & ")", LogLevel.Feedback)
         Finally
         Finally
             AniControlEnabled -= 1
             AniControlEnabled -= 1
         End Try
         End Try

+ 9 - 3
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb

@@ -13,12 +13,18 @@
     Private Shared Function LoaderInput() As CompProjectRequest
     Private Shared Function LoaderInput() As CompProjectRequest
         Dim Request As New CompProjectRequest(CompType.Mod, Storage, (Page + 1) * PageSize)
         Dim Request As New CompProjectRequest(CompType.Mod, Storage, (Page + 1) * PageSize)
         If FrmDownloadMod IsNot Nothing Then
         If FrmDownloadMod IsNot Nothing Then
+            Dim ModLoader As CompModLoaderType = Val(FrmDownloadMod.ComboSearchLoader.SelectedItem.Tag)
+            Dim GameVersion As String = If(FrmDownloadMod.TextSearchVersion.Text = "全部 (也可自行输入)", Nothing,
+                    If(FrmDownloadMod.TextSearchVersion.Text.Contains(".") OrElse FrmDownloadMod.TextSearchVersion.Text.Contains("w"), FrmDownloadMod.TextSearchVersion.Text, Nothing))
+            If GameVersion IsNot Nothing AndAlso GameVersion.Contains(".") AndAlso Val(GameVersion.Split(".")(1)) < 14 AndAlso '1.14-
+                ModLoader = CompModLoaderType.Forge Then '选择了 Forge
+                ModLoader = CompModLoaderType.Any '此时,视作没有筛选 Mod Loader(因为部分老 Mod 没有设置自己支持的加载器)
+            End If
             With Request
             With Request
                 .SearchText = FrmDownloadMod.TextSearchName.Text
                 .SearchText = FrmDownloadMod.TextSearchName.Text
-                .GameVersion = If(FrmDownloadMod.TextSearchVersion.Text = "全部 (也可自行输入)", Nothing,
-                    If(FrmDownloadMod.TextSearchVersion.Text.Contains(".") OrElse FrmDownloadMod.TextSearchVersion.Text.Contains("w"), FrmDownloadMod.TextSearchVersion.Text, Nothing))
+                .GameVersion = GameVersion
                 .Tag = FrmDownloadMod.ComboSearchTag.SelectedItem.Tag
                 .Tag = FrmDownloadMod.ComboSearchTag.SelectedItem.Tag
-                .ModLoader = Val(FrmDownloadMod.ComboSearchLoader.SelectedItem.Tag)
+                .ModLoader = ModLoader
                 .Source = CType(Val(FrmDownloadMod.ComboSearchSource.SelectedItem.Tag), CompSourceType)
                 .Source = CType(Val(FrmDownloadMod.ComboSearchSource.SelectedItem.Tag), CompSourceType)
             End With
             End With
         End If
         End If

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

@@ -126,7 +126,7 @@
                     Finished(New Exception("$登录用时太长啦,重新试试吧!"))
                     Finished(New Exception("$登录用时太长啦,重新试试吧!"))
                     Return
                     Return
                 ElseIf ex.Message.Contains("AADSTS70000") Then '可能不能判 “invalid_grant”,见 #269
                 ElseIf ex.Message.Contains("AADSTS70000") Then '可能不能判 “invalid_grant”,见 #269
-                    Finished(New RetryException)
+                    Finished(New RestartException)
                     Return
                     Return
                 ElseIf ex.Message.Contains("authorization_pending") Then
                 ElseIf ex.Message.Contains("authorization_pending") Then
                     Thread.Sleep(2000)
                     Thread.Sleep(2000)

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

@@ -177,8 +177,8 @@
         Sub()
         Sub()
             Try
             Try
                 '更新缓存
                 '更新缓存
-                WriteIni(PathTemp & "Cache\Skin\IndexMs.ini", Setup.Get("CacheMsUuid"), SkinAddress)
-                Log(String.Format("[Skin] 已写入皮肤地址缓存 {0} -> {1}", Setup.Get("CacheMsUuid"), SkinAddress))
+                WriteIni(PathTemp & "Cache\Skin\IndexMs.ini", Setup.Get("CacheMsV2Uuid"), SkinAddress)
+                Log(String.Format("[Skin] 已写入皮肤地址缓存 {0} -> {1}", Setup.Get("CacheMsV2Uuid"), SkinAddress))
                 '刷新控件
                 '刷新控件
                 For Each SkinLoader In {PageLaunchLeft.SkinMs, PageLaunchLeft.SkinLegacy}
                 For Each SkinLoader In {PageLaunchLeft.SkinMs, PageLaunchLeft.SkinLegacy}
                     SkinLoader.WaitForExit(IsForceRestart:=True)
                     SkinLoader.WaitForExit(IsForceRestart:=True)

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

@@ -296,7 +296,7 @@
             Case 0 '正版或离线
             Case 0 '正版或离线
 UnknownType:
 UnknownType:
                 If RadioLoginType5.Checked Then
                 If RadioLoginType5.Checked Then
-                    If Setup.Get("CacheMsAccess") = "" Then
+                    If Setup.Get("CacheMsV2Access") = "" Then
                         Type = PageType.Ms
                         Type = PageType.Ms
                     Else
                     Else
                         Type = PageType.MsSkin
                         Type = PageType.MsSkin
@@ -311,7 +311,7 @@ UnknownType:
                 RadioLoginType5.Visibility = Visibility.Visible
                 RadioLoginType5.Visibility = Visibility.Visible
                 RadioLoginType0.Visibility = Visibility.Visible
                 RadioLoginType0.Visibility = Visibility.Visible
             Case 1 '仅正版
             Case 1 '仅正版
-                If Setup.Get("CacheMsAccess") = "" Then
+                If Setup.Get("CacheMsV2Access") = "" Then
                     Type = PageType.Ms
                     Type = PageType.Ms
                 Else
                 Else
                     Type = PageType.MsSkin
                     Type = PageType.MsSkin
@@ -375,7 +375,7 @@ UnknownType:
     Public Shared SkinMs As New LoaderTask(Of EqualableList(Of String), String)("Loader Skin Ms", AddressOf SkinMsLoad, AddressOf SkinMsInput, ThreadPriority.AboveNormal)
     Public Shared SkinMs As New LoaderTask(Of EqualableList(Of String), String)("Loader Skin Ms", AddressOf SkinMsLoad, AddressOf SkinMsInput, ThreadPriority.AboveNormal)
     Private Shared Function SkinMsInput() As EqualableList(Of String)
     Private Shared Function SkinMsInput() As EqualableList(Of String)
         '获取名称
         '获取名称
-        Return New EqualableList(Of String) From {Setup.Get("CacheMsName"), Setup.Get("CacheMsUuid")}
+        Return New EqualableList(Of String) From {Setup.Get("CacheMsV2Name"), Setup.Get("CacheMsV2Uuid")}
     End Function
     End Function
     Private Shared Sub SkinMsLoad(Data As LoaderTask(Of EqualableList(Of String), String))
     Private Shared Sub SkinMsLoad(Data As LoaderTask(Of EqualableList(Of String), String))
         '清空已有皮肤
         '清空已有皮肤

+ 8 - 0
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb

@@ -114,6 +114,14 @@ Download:
                         Log("[Page] 主页预设:更新日志")
                         Log("[Page] 主页预设:更新日志")
                         Url = "https://updatehomepage.pages.dev/UpdateHomepage.xaml"
                         Url = "https://updatehomepage.pages.dev/UpdateHomepage.xaml"
                         GoTo Download
                         GoTo Download
+                    Case 9
+                        Log("[Page] 主页预设:PCL 新功能说明书")
+                        Url = "https://gitee.com/wforstbreeze/whats-new-pcl/raw/main/Custom.xaml"
+                        GoTo Download
+                    Case 10
+                        Log("[Page] 主页预设:OpenMCIM Dashboard")
+                        Url = "https://files.mcimirror.top/PCL"
+                        GoTo Download
                 End Select
                 End Select
         End Select
         End Select
         RunInUi(Sub() LoadContent(Content))
         RunInUi(Sub() LoadContent(Content))

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

@@ -37,7 +37,7 @@
     ''' 获取当前页面的登录信息。
     ''' 获取当前页面的登录信息。
     ''' </summary>
     ''' </summary>
     Public Shared Function GetLoginData() As McLoginMs
     Public Shared Function GetLoginData() As McLoginMs
-        If FrmLoginMs Is Nothing Then Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsOAuthRefresh"), .UserName = Setup.Get("CacheMsName")}
+        If FrmLoginMs Is Nothing Then Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsV2OAuthRefresh"), .UserName = Setup.Get("CacheMsV2Name")}
         Dim Result As McLoginMs = Nothing
         Dim Result As McLoginMs = Nothing
         RunInUiWait(
         RunInUiWait(
         Sub()
         Sub()

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

@@ -12,7 +12,7 @@
     ''' 刷新页面显示的所有信息。
     ''' 刷新页面显示的所有信息。
     ''' </summary>
     ''' </summary>
     Public Sub Reload(KeepInput As Boolean)
     Public Sub Reload(KeepInput As Boolean)
-        TextName.Text = Setup.Get("CacheMsName")
+        TextName.Text = Setup.Get("CacheMsV2Name")
         '皮肤在 Loaded 加载
         '皮肤在 Loaded 加载
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
@@ -20,9 +20,9 @@
     ''' </summary>
     ''' </summary>
     Public Shared Function GetLoginData() As McLoginMs
     Public Shared Function GetLoginData() As McLoginMs
         If McLoginMsLoader.State = LoadState.Finished Then
         If McLoginMsLoader.State = LoadState.Finished Then
-            Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsOAuthRefresh"), .UserName = Setup.Get("CacheMsName"), .AccessToken = Setup.Get("CacheMsAccess"), .Uuid = Setup.Get("CacheMsUuid"), .ProfileJson = Setup.Get("CacheMsProfileJson")}
+            Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsV2OAuthRefresh"), .UserName = Setup.Get("CacheMsV2Name"), .AccessToken = Setup.Get("CacheMsV2Access"), .Uuid = Setup.Get("CacheMsV2Uuid"), .ProfileJson = Setup.Get("CacheMsV2ProfileJson")}
         Else
         Else
-            Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsOAuthRefresh"), .UserName = Setup.Get("CacheMsName")}
+            Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsV2OAuthRefresh"), .UserName = Setup.Get("CacheMsV2Name")}
         End If
         End If
     End Function
     End Function
 
 
@@ -50,11 +50,11 @@
 
 
     '退出登录
     '退出登录
     Private Sub BtnExit_Click() Handles BtnExit.Click
     Private Sub BtnExit_Click() Handles BtnExit.Click
-        Setup.Set("CacheMsOAuthRefresh", "")
-        Setup.Set("CacheMsAccess", "")
-        Setup.Set("CacheMsProfileJson", "")
-        Setup.Set("CacheMsUuid", "")
-        Setup.Set("CacheMsName", "")
+        Setup.Set("CacheMsV2OAuthRefresh", "")
+        Setup.Set("CacheMsV2Access", "")
+        Setup.Set("CacheMsV2ProfileJson", "")
+        Setup.Set("CacheMsV2Uuid", "")
+        Setup.Set("CacheMsV2Name", "")
         McLoginMsLoader.Abort()
         McLoginMsLoader.Abort()
         FrmLaunchLeft.RefreshPage(False, True)
         FrmLaunchLeft.RefreshPage(False, True)
     End Sub
     End Sub
@@ -90,8 +90,8 @@
             Try
             Try
 Retry:
 Retry:
                 If McLoginMsLoader.State = LoadState.Loading Then McLoginMsLoader.WaitForExit() '等待登录结束
                 If McLoginMsLoader.State = LoadState.Loading Then McLoginMsLoader.WaitForExit() '等待登录结束
-                Dim AccessToken As String = Setup.Get("CacheMsAccess")
-                Dim Uuid As String = Setup.Get("CacheMsUuid")
+                Dim AccessToken As String = Setup.Get("CacheMsV2Access")
+                Dim Uuid As String = Setup.Get("CacheMsV2Uuid")
 
 
                 Dim Client As New Net.Http.HttpClient With {.Timeout = New TimeSpan(0, 0, 30)}
                 Dim Client As New Net.Http.HttpClient With {.Timeout = New TimeSpan(0, 0, 30)}
                 Client.DefaultRequestHeaders.Authorization = New Net.Http.Headers.AuthenticationHeaderValue("Bearer", AccessToken)
                 Client.DefaultRequestHeaders.Authorization = New Net.Http.Headers.AuthenticationHeaderValue("Bearer", AccessToken)

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

@@ -419,75 +419,6 @@ Public Class PageLinkIoi
 
 
     '获取数据包
     '获取数据包
     Public Shared Sub ReceiveJson(JsonData As JObject, Optional NewSocket As Socket = Nothing)
     Public Shared Sub ReceiveJson(JsonData As JObject, Optional NewSocket As Socket = Nothing)
-        '获取数据
-        Dim Id As String = JsonData("id"), Type As String = JsonData("type")
-        Select Case Type
-            Case "connect"
-                Dim DisplayName As String = JsonData("name")
-                Dim User As New LinkUserIoi(Id, DisplayName, NewSocket)
-                '如果发生了双向连接
-                If UserList.ContainsKey(Id) Then
-                    If Id > IoiId Then
-                        Log("[IOI] 双向连接,应当抛弃当前用户(" & DisplayName & ")")
-                        For Each Pair In UserList(User.Id).Ports.ToList
-                            User.Ports(Pair.Key) = Pair.Value
-                        Next
-                        UserList(Id).Dispose()
-                    Else
-                        '应当保留当前用户
-                        Log("[IOI] 双向连接,应当保留当前用户(" & DisplayName & ")")
-                        NewSocket.Dispose()
-                        Exit Sub
-                    End If
-                End If
-                '加入列表
-                UserList.Add(Id, User)
-                '返回请求
-                User.RelativeThread = RunInNewThread(Sub()
-                                                         SendPortsubBack(User, JsonData("version").ToObject(Of Integer))
-                                                     End Sub, "Link Connect " & DisplayName)
-                '更新时间
-                User.LastReceive = Date.Now
-            Case "update"
-                If Not UserList.ContainsKey(Id) Then Throw New Exception("未在列表中的用户发送了更新请求:" & Id)
-                Dim User = UserList(Id)
-                '拉满进度(该请求也作为反向连接回应出现,用于向正向连接方传达连接已完成信号)
-                User.Progress = 1
-                '更新名称
-                Dim DisplayName As String = JsonData("name")
-                User.DisplayName = DisplayName
-                '更新房间列表
-                If JsonData("rooms") IsNot Nothing Then
-                    User.Rooms = New List(Of RoomEntry)
-                    For Each RoomObject In JsonData("rooms")
-                        User.Rooms.Add(New RoomEntry(RoomObject("port"), RoomObject("name"), User))
-                    Next
-                End If
-                '更新 Ping
-                Dim Stage As Integer = JsonData("stage"), Unique As Long = JsonData("unique")
-                If Stage > 1 Then
-                    User.PingRecord.Enqueue((Date.Now - User.PingPending(Unique)).TotalMilliseconds / 2)
-                    If User.PingRecord.Count > 5 Then User.PingRecord.Dequeue()
-                    User.PingPending.Remove(Unique)
-                End If
-                '返回请求
-                If Stage > 0 AndAlso Stage < 3 Then RunInNewThread(Sub()
-                                                                       Try
-                                                                           SendUpdateRequest(User, Stage + 1, Unique)
-                                                                       Catch ex As Exception
-                                                                           Log(ex, "发送回程请求失败")
-                                                                       End Try
-                                                                   End Sub, "Link Update " & DisplayName)
-                '更新时间
-                User.LastReceive = Date.Now
-            Case "disconnect"
-                '断开连接
-                If Not UserList.ContainsKey(Id) Then Exit Sub
-                UserRemove(UserList(Id), ShowLeaveMessage:=JsonData("message") Is Nothing)
-                If JsonData("message") IsNot Nothing Then Hint(JsonData("message").ToString, If(JsonData("isError").ToObject(Of Boolean), HintType.Critical, HintType.Info))
-            Case Else
-                Throw New Exception("未知的操作种类:" & Type)
-        End Select
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
     ''' 从用户列表中移除一位用户。提示信息视作该用户主动离开。
     ''' 从用户列表中移除一位用户。提示信息视作该用户主动离开。

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

@@ -63,7 +63,7 @@
             PageChangeRun(PageGet(ID))
             PageChangeRun(PageGet(ID))
             PageID = ID
             PageID = ID
         Catch ex As Exception
         Catch ex As Exception
-            Log(ex, "切换设置分页面失败(ID " & ID & ")", LogLevel.Feedback)
+            Log(ex, "切换分页面失败(ID " & ID & ")", LogLevel.Feedback)
         Finally
         Finally
             AniControlEnabled -= 1
             AniControlEnabled -= 1
         End Try
         End Try

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

@@ -83,7 +83,7 @@
             PageChangeRun(PageGet(ID))
             PageChangeRun(PageGet(ID))
             PageID = ID
             PageID = ID
         Catch ex As Exception
         Catch ex As Exception
-            Log(ex, "切换设置分页面失败(ID " & ID & ")", LogLevel.Feedback)
+            Log(ex, "切换分页面失败(ID " & ID & ")", LogLevel.Feedback)
         Finally
         Finally
             AniControlEnabled -= 1
             AniControlEnabled -= 1
         End Try
         End Try
@@ -127,8 +127,8 @@
     '打开网页
     '打开网页
     Public Shared Sub TryFeedback() Handles ItemFeedback.Click
     Public Shared Sub TryFeedback() Handles ItemFeedback.Click
         If Not CanFeedback(True) Then Exit Sub
         If Not CanFeedback(True) Then Exit Sub
-        Select Case MyMsgBox("是否要打开反馈列表网页?" & vbCrLf & "如果无法打开该网页,请尝试使用加速器或 VPN。",
-                    "反馈提示", "提交新反馈", "查看反馈列表", "取消")
+        Select Case MyMsgBox("在提交新反馈前,建议先搜索反馈列表,以避免重复提交。" & vbCrLf & "如果无法打开该网页,请尝试使用加速器或 VPN。",
+                    "反馈", "提交新反馈", "查看反馈列表", "取消")
             Case 1
             Case 1
                 Feedback(True, False)
                 Feedback(True, False)
             Case 2
             Case 2
@@ -137,7 +137,7 @@
     End Sub
     End Sub
     Public Shared Sub TryVote() Handles ItemVote.Click
     Public Shared Sub TryVote() Handles ItemVote.Click
         If MyMsgBox("是否要打开新功能投票网页?" & vbCrLf & "如果无法打开该网页,请尝试使用加速器或 VPN。",
         If MyMsgBox("是否要打开新功能投票网页?" & vbCrLf & "如果无法打开该网页,请尝试使用加速器或 VPN。",
-                    "提醒", "打开", "取消") = 2 Then Exit Sub
+                    "新功能投票", "打开", "取消") = 2 Then Exit Sub
         OpenWebsite("https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8?discussions_q=category%3A%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8+sort%3Adate_created")
         OpenWebsite("https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8?discussions_q=category%3A%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8+sort%3Adate_created")
     End Sub
     End Sub
 
 

+ 10 - 2
Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb

@@ -9,6 +9,8 @@
     ''' </summary>
     ''' </summary>
     Private ReadOnly SetupDict As New Dictionary(Of String, SetupEntry) From {
     Private ReadOnly SetupDict As New Dictionary(Of String, SetupEntry) From {
         {"Identify", New SetupEntry("", Source:=SetupSource.Registry)},
         {"Identify", New SetupEntry("", Source:=SetupSource.Registry)},
+        {"WindowHeight", New SetupEntry(550)},
+        {"WindowWidth", New SetupEntry(900)},
         {"HintDownloadThread", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"HintDownloadThread", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"HintNotice", New SetupEntry(0, Source:=SetupSource.Registry)},
         {"HintNotice", New SetupEntry(0, Source:=SetupSource.Registry)},
         {"HintDownload", New SetupEntry(0, Source:=SetupSource.Registry)},
         {"HintDownload", New SetupEntry(0, Source:=SetupSource.Registry)},
@@ -42,6 +44,12 @@
         {"CacheMsProfileJson", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
         {"CacheMsProfileJson", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
         {"CacheMsUuid", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
         {"CacheMsUuid", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
         {"CacheMsName", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
         {"CacheMsName", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
+        {"CacheMsV2Migrated", New SetupEntry(False, Source:=SetupSource.Registry)},
+        {"CacheMsV2OAuthRefresh", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
+        {"CacheMsV2Access", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
+        {"CacheMsV2ProfileJson", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
+        {"CacheMsV2Uuid", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
+        {"CacheMsV2Name", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
         {"CacheNideAccess", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
         {"CacheNideAccess", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
         {"CacheNideClient", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
         {"CacheNideClient", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
         {"CacheNideUuid", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
         {"CacheNideUuid", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
@@ -105,10 +113,10 @@
         {"ToolDownloadSpeed", New SetupEntry(42, Source:=SetupSource.Registry)},
         {"ToolDownloadSpeed", New SetupEntry(42, Source:=SetupSource.Registry)},
         {"ToolDownloadVersion", New SetupEntry(0, Source:=SetupSource.Registry)},
         {"ToolDownloadVersion", New SetupEntry(0, Source:=SetupSource.Registry)},
         {"ToolDownloadTranslate", New SetupEntry(0, Source:=SetupSource.Registry)},
         {"ToolDownloadTranslate", New SetupEntry(0, Source:=SetupSource.Registry)},
-        {"ToolDownloadKeepModpack", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolDownloadIgnoreQuilt", New SetupEntry(True, Source:=SetupSource.Registry)},
         {"ToolDownloadIgnoreQuilt", New SetupEntry(True, Source:=SetupSource.Registry)},
         {"ToolDownloadCert", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolDownloadCert", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolDownloadMod", New SetupEntry(1, Source:=SetupSource.Registry)},
         {"ToolDownloadMod", New SetupEntry(1, Source:=SetupSource.Registry)},
+        {"ToolModLocalNameStyle", New SetupEntry(0, Source:=SetupSource.Registry)},
         {"ToolUpdateAlpha", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
         {"ToolUpdateAlpha", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
         {"ToolUpdateRelease", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolUpdateRelease", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolUpdateSnapshot", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolUpdateSnapshot", New SetupEntry(False, Source:=SetupSource.Registry)},
@@ -592,7 +600,7 @@
                     FrmSetupUI.PanLogoChange.Visibility = Visibility.Visible
                     FrmSetupUI.PanLogoChange.Visibility = Visibility.Visible
                 End If
                 End If
                 Try
                 Try
-                    FrmMain.ImageTitleLogo.Source = New MyBitmap(Path & "PCL\Logo.png")
+                    FrmMain.ImageTitleLogo.Source = Path & "PCL\Logo.png"
                 Catch ex As Exception
                 Catch ex As Exception
                     FrmMain.ImageTitleLogo.Source = Nothing
                     FrmMain.ImageTitleLogo.Source = Nothing
                     Log(ex, "显示标题栏图片失败", LogLevel.Msgbox)
                     Log(ex, "显示标题栏图片失败", LogLevel.Msgbox)

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

@@ -115,7 +115,7 @@
 
 
             PageID = ID
             PageID = ID
         Catch ex As Exception
         Catch ex As Exception
-            Log(ex, "切换设置分页面失败(ID " & ID & ")", LogLevel.Feedback)
+            Log(ex, "切换分页面失败(ID " & ID & ")", LogLevel.Feedback)
         Finally
         Finally
             AniControlEnabled -= 1
             AniControlEnabled -= 1
         End Try
         End Try

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

@@ -7,66 +7,80 @@
     PanScroll="{Binding ElementName=PanBack}">
     PanScroll="{Binding ElementName=PanBack}">
     <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
     <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
         <StackPanel x:Name="PanMain" Margin="25,10">
         <StackPanel x:Name="PanMain" Margin="25,10">
-
             <local:MyCard Margin="0,15" Title="下载">
             <local:MyCard Margin="0,15" Title="下载">
-                <StackPanel Margin="25,37,25,12">
+                <StackPanel Margin="25,37,25,15">
                     <Grid>
                     <Grid>
                         <Grid.ColumnDefinitions>
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
                             <ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
                             <ColumnDefinition />
                             <ColumnDefinition />
                         </Grid.ColumnDefinitions>
                         </Grid.ColumnDefinitions>
                         <Grid.RowDefinitions>
                         <Grid.RowDefinitions>
-                            <RowDefinition Height="28" />
-                            <RowDefinition Height="7" />
-                            <RowDefinition Height="28" />
-                            <RowDefinition Height="7" />
                             <RowDefinition Height="28" />
                             <RowDefinition Height="28" />
                             <RowDefinition Height="7" />
                             <RowDefinition Height="7" />
                             <RowDefinition Height="27" />
                             <RowDefinition Height="27" />
                             <RowDefinition Height="27" />
                             <RowDefinition Height="27" />
                             <RowDefinition Height="Auto" />
                             <RowDefinition Height="Auto" />
                             <RowDefinition Height="Auto" />
                             <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
                         </Grid.RowDefinitions>
                         </Grid.RowDefinitions>
-                        <TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Text="版本列表获取" Margin="0,0,25,0" />
+                        <TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Text="版本列表源" Margin="0,0,25,0" />
                         <local:MyComboBox x:Name="ComboDownloadVersion" Grid.ColumnSpan="2" Tag="ToolDownloadVersion" Grid.Column="1">
                         <local:MyComboBox x:Name="ComboDownloadVersion" Grid.ColumnSpan="2" Tag="ToolDownloadVersion" Grid.Column="1">
                             <local:MyComboBoxItem Content="尽量使用镜像源(加载快,但版本可能不是最新的)" IsSelected="True" />
                             <local:MyComboBoxItem Content="尽量使用镜像源(加载快,但版本可能不是最新的)" IsSelected="True" />
                             <local:MyComboBoxItem Content="仅在官方源加载缓慢时改用镜像源" />
                             <local:MyComboBoxItem Content="仅在官方源加载缓慢时改用镜像源" />
                             <local:MyComboBoxItem Content="尽量使用官方源(保证版本最新,但加载缓慢)" />
                             <local:MyComboBoxItem Content="尽量使用官方源(保证版本最新,但加载缓慢)" />
                         </local:MyComboBox>
                         </local:MyComboBox>
-                        <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="Mod 信息获取" Margin="0,0,25,0" />
-                        <local:MyComboBox x:Name="ComboDownloadMod" Grid.Row="2" Grid.ColumnSpan="2" Tag="ToolDownloadMod" Grid.Column="1">
-                            <local:MyComboBoxItem Content="尽量使用镜像源" />
+                        <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="最大线程数" Margin="0,0,25,0" />
+                        <local:MySlider x:Name="SliderDownloadThread" Grid.Row="2" Tag="ToolDownloadThread" MaxValue="255" Value="63" Grid.Column="1"
+                                        ToolTip="线程数越多,限速的文件下载越快,但过高的线程数会造成下载时非常严重的卡顿。&#xa;一般而言,64 线程已可以保证足够的下载速度。" />
+                        <TextBlock VerticalAlignment="Center" Grid.Row="3" HorizontalAlignment="Left" Text="速度限制" Margin="0,0,25,0" />
+                        <local:MySlider x:Name="SliderDownloadSpeed" Grid.Row="3" Tag="ToolDownloadSpeed" MaxValue="42" Value="42" Grid.Column="1"
+                                        ToolTip="设置下载的速度上限,以避免在下载时导致其他需要联网的程序卡死" />
+                        <TextBlock VerticalAlignment="Top" Grid.Row="4" HorizontalAlignment="Left" Text="目标文件夹" Margin="0,5,25,5" />
+                        <TextBlock Margin="0,5" HorizontalAlignment="Left" Text="请在 启动 → 版本选择 → 文件夹列表 中更改下载目标文件夹。&#xa;在某个文件夹或游戏版本上右键,即可选择打开对应文件夹。" Grid.Row="4" Grid.Column="1" VerticalAlignment="Center" Opacity="0.5" />
+                        <local:MyCheckBox Margin="0,2,0,4" Text="验证 SSL 证书" Grid.Row="5" Height="22" Grid.ColumnSpan="2"
+                                          x:Name="CheckDownloadCert" Tag="ToolDownloadCert" 
+                                          ToolTip="开启验证会提高安全性、降低盗号风险(见 #2767),但也可能导致正版登录失败(见 #3018)。" />
+                    </Grid>
+                </StackPanel>
+            </local:MyCard>
+            <local:MyCard Margin="0,0,0,15" Title="Mod 与整合包">
+                <StackPanel Margin="25,37,25,15">
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
+                            <ColumnDefinition />
+                        </Grid.ColumnDefinitions>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="28" />
+                            <RowDefinition Height="7" />
+                            <RowDefinition Height="28" />
+                            <RowDefinition Height="7" />
+                            <RowDefinition Height="28" />
+                            <RowDefinition Height="7" />
+                            <RowDefinition Height="Auto" />
+                        </Grid.RowDefinitions>
+                        <TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="下载源" Margin="0,0,25,0" />
+                        <local:MyComboBox x:Name="ComboDownloadMod" Grid.Row="0" Grid.ColumnSpan="2" Tag="ToolDownloadMod" Grid.Column="1">
+                            <local:MyComboBoxItem Content="尽量使用镜像源(暂时无效)" />
                             <local:MyComboBoxItem Content="仅在官方源加载缓慢时改用镜像源" />
                             <local:MyComboBoxItem Content="仅在官方源加载缓慢时改用镜像源" />
                             <local:MyComboBoxItem Content="尽量使用官方源" IsSelected="True" />
                             <local:MyComboBoxItem Content="尽量使用官方源" IsSelected="True" />
                         </local:MyComboBox>
                         </local:MyComboBox>
-                        <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="Mod 命名风格" Margin="0,0,25,0" />
-                        <local:MyComboBox Grid.Row="4" x:Name="ComboDownloadTranslate" Grid.ColumnSpan="2" Tag="ToolDownloadTranslate" Grid.Column="1" 
-                                          ToolTip="决定下载的 Mod 的默认文件名中,中文译名应该放在哪个位置">
+                        <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="下载文件名" Margin="0,0,25,0" />
+                        <local:MyComboBox Grid.Row="2" x:Name="ComboDownloadTranslate" Grid.ColumnSpan="2" Tag="ToolDownloadTranslate" Grid.Column="1" 
+                                          ToolTip="下载 Mod 时,Mod 的默认文件名中,中文译名应该放在哪个位置">
                             <local:MyComboBoxItem Content="译名位于文件名开头:[遗物] relics-0.1.14" IsSelected="True" />
                             <local:MyComboBoxItem Content="译名位于文件名开头:[遗物] relics-0.1.14" IsSelected="True" />
                             <local:MyComboBoxItem Content="译名位于文件名开头:遗物-relics-0.1.14" />
                             <local:MyComboBoxItem Content="译名位于文件名开头:遗物-relics-0.1.14" />
                             <local:MyComboBoxItem Content="译名位于文件名末尾:relics-0.1.14-遗物" />
                             <local:MyComboBoxItem Content="译名位于文件名末尾:relics-0.1.14-遗物" />
                             <local:MyComboBoxItem Content="不添加中文译名:relics-0.1.14" />
                             <local:MyComboBoxItem Content="不添加中文译名:relics-0.1.14" />
                         </local:MyComboBox>
                         </local:MyComboBox>
-                        <TextBlock Grid.Row="6" VerticalAlignment="Center" HorizontalAlignment="Left" Text="最大线程数" Margin="0,0,25,0" />
-                        <local:MySlider x:Name="SliderDownloadThread" Grid.Row="6" Tag="ToolDownloadThread" MaxValue="255" Value="63" Grid.Column="1"
-                                        ToolTip="线程数越多,限速的文件下载越快,但过高的线程数会造成下载时非常严重的卡顿。&#xa;一般而言,64 线程已可以保证足够的下载速度。" />
-                        <TextBlock VerticalAlignment="Center" Grid.Row="7" HorizontalAlignment="Left" Text="速度限制" Margin="0,0,25,0" />
-                        <local:MySlider x:Name="SliderDownloadSpeed" Grid.Row="7" Tag="ToolDownloadSpeed" MaxValue="42" Value="42" Grid.Column="1"
-                                        ToolTip="设置下载的速度上限,以避免在下载时导致其他需要联网的程序卡死" />
-                        <TextBlock VerticalAlignment="Top" Grid.Row="9" HorizontalAlignment="Left" Text="目标文件夹" Margin="0,5,25,5" />
-                        <TextBlock Margin="0,5" HorizontalAlignment="Left" Text="请在 启动 → 版本选择 → 文件夹列表 中更改下载目标文件夹。&#xa;在某个文件夹或游戏版本上右键,即可选择打开对应文件夹。" Grid.Row="9" Grid.Column="1" VerticalAlignment="Center" Opacity="0.5" />
-                        <local:MyCheckBox Margin="0,5,0,4" Text="安装整合包时保留原始压缩包" Grid.Row="10" Height="22" Grid.ColumnSpan="2"
-                                          x:Name="CheckDownloadKeepModpack" Tag="ToolDownloadKeepModpack" 
-                                          ToolTip="安装整合包时,在版本文件夹下保留用于安装的原始整合包压缩包文件" />
-                        <local:MyCheckBox Margin="0,2,0,4" Text="简化 Mod 加载器显示" Grid.Row="11" Height="22" Grid.ColumnSpan="2"
+                        <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="Mod 管理样式" Margin="0,0,25,0" />
+                        <local:MyComboBox Grid.Row="4" x:Name="ComboModLocalNameStyle" Grid.ColumnSpan="2" Tag="ToolModLocalNameStyle" Grid.Column="1" 
+                                          ToolTip="在 Mod 管理页面中,Mod 项的显示方式">
+                            <local:MyComboBoxItem Content="标题显示译名,详情显示文件名" IsSelected="True" />
+                            <local:MyComboBoxItem Content="标题显示文件名,详情显示译名" />
+                        </local:MyComboBox>
+                        <local:MyCheckBox Margin="0,2,0,4" Text="简化 Mod 加载器显示" Grid.Row="6" Height="22" Grid.ColumnSpan="2"
                                           x:Name="CheckDownloadIgnoreQuilt" Tag="ToolDownloadIgnoreQuilt" 
                                           x:Name="CheckDownloadIgnoreQuilt" Tag="ToolDownloadIgnoreQuilt" 
                                           ToolTip="下载 Mod 时,若该 Mod 支持 Forge 和 Fabric,则显示为支持任意 Mod 加载器。&#xa;例如,开启时 JEI 显示支持全版本,关闭后 JEI 就会显示仅支持 Forge / Fabric 全版本。" />
                                           ToolTip="下载 Mod 时,若该 Mod 支持 Forge 和 Fabric,则显示为支持任意 Mod 加载器。&#xa;例如,开启时 JEI 显示支持全版本,关闭后 JEI 就会显示仅支持 Forge / Fabric 全版本。" />
-                        <local:MyCheckBox Margin="0,2,0,4" Text="验证 SSL 证书" Grid.Row="12" Height="22" Grid.ColumnSpan="2"
-                                          x:Name="CheckDownloadCert" Tag="ToolDownloadCert" 
-                                          ToolTip="开启验证会提高安全性、降低盗号风险(见 #2767),但也可能导致正版登录失败(见 #3018)。" />
                     </Grid>
                     </Grid>
                 </StackPanel>
                 </StackPanel>
             </local:MyCard>
             </local:MyCard>

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

@@ -1,33 +1,5 @@
 Class PageSetupSystem
 Class PageSetupSystem
 
 
-#Region "语言"
-    'Private Sub PageSetupUI_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
-    '  AniControlEnabled -= 1
-
-    '  '读取设置
-    '  Select Case Lang
-    '      Case "zh_CN"
-    '          ComboLang.SelectedIndex = 0
-    '      Case "zh_HK"
-    '          ComboLang.SelectedIndex = 1
-    '      Case "en_US"
-    '          ComboLang.SelectedIndex = 2
-    '  End Select
-    '  CheckDebug.Checked = ReadReg("SystemDebugMode", "False")
-
-    '  AniControlEnabled += 1
-    'End Sub
-
-    'Private Sub RefreshLang() Handles ComboLang.SelectionChanged
-    '  If IsLoaded Then
-    '      If Not ComboLang.IsLoaded Then Exit Sub
-    '      Lang = CType(ComboLang.SelectedItem, MyComboBoxItem).Tag
-    '      Application.Current.Resources.MergedDictionaries(1) = New ResourceDictionary With {.Source = New Url("Languages\" & Lang & ".xaml", UrlKind.Relative)}
-    '      WriteReg("Lang", Lang)
-    '  End If
-    'End Sub
-#End Region
-
     Private Shadows IsLoaded As Boolean = False
     Private Shadows IsLoaded As Boolean = False
 
 
     Private Sub PageSetupSystem_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
     Private Sub PageSetupSystem_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
@@ -58,11 +30,13 @@
         SliderDownloadThread.Value = Setup.Get("ToolDownloadThread")
         SliderDownloadThread.Value = Setup.Get("ToolDownloadThread")
         SliderDownloadSpeed.Value = Setup.Get("ToolDownloadSpeed")
         SliderDownloadSpeed.Value = Setup.Get("ToolDownloadSpeed")
         ComboDownloadVersion.SelectedIndex = Setup.Get("ToolDownloadVersion")
         ComboDownloadVersion.SelectedIndex = Setup.Get("ToolDownloadVersion")
+        CheckDownloadCert.Checked = Setup.Get("ToolDownloadCert")
+
+        'Mod 与整合包
         ComboDownloadTranslate.SelectedIndex = Setup.Get("ToolDownloadTranslate")
         ComboDownloadTranslate.SelectedIndex = Setup.Get("ToolDownloadTranslate")
         ComboDownloadMod.SelectedIndex = Setup.Get("ToolDownloadMod")
         ComboDownloadMod.SelectedIndex = Setup.Get("ToolDownloadMod")
-        CheckDownloadKeepModpack.Checked = Setup.Get("ToolDownloadKeepModpack")
+        ComboModLocalNameStyle.SelectedIndex = Setup.Get("ToolModLocalNameStyle")
         CheckDownloadIgnoreQuilt.Checked = Setup.Get("ToolDownloadIgnoreQuilt")
         CheckDownloadIgnoreQuilt.Checked = Setup.Get("ToolDownloadIgnoreQuilt")
-        CheckDownloadCert.Checked = Setup.Get("ToolDownloadCert")
 
 
         'Minecraft 更新提示
         'Minecraft 更新提示
         CheckUpdateRelease.Checked = Setup.Get("ToolUpdateRelease")
         CheckUpdateRelease.Checked = Setup.Get("ToolUpdateRelease")
@@ -91,10 +65,10 @@
             Setup.Reset("ToolDownloadSpeed")
             Setup.Reset("ToolDownloadSpeed")
             Setup.Reset("ToolDownloadVersion")
             Setup.Reset("ToolDownloadVersion")
             Setup.Reset("ToolDownloadTranslate")
             Setup.Reset("ToolDownloadTranslate")
-            Setup.Reset("ToolDownloadKeepModpack")
             Setup.Reset("ToolDownloadIgnoreQuilt")
             Setup.Reset("ToolDownloadIgnoreQuilt")
             Setup.Reset("ToolDownloadCert")
             Setup.Reset("ToolDownloadCert")
             Setup.Reset("ToolDownloadMod")
             Setup.Reset("ToolDownloadMod")
+            Setup.Reset("ToolModLocalNameStyle")
             Setup.Reset("ToolUpdateRelease")
             Setup.Reset("ToolUpdateRelease")
             Setup.Reset("ToolUpdateSnapshot")
             Setup.Reset("ToolUpdateSnapshot")
             Setup.Reset("ToolHelpChinese")
             Setup.Reset("ToolHelpChinese")
@@ -116,13 +90,13 @@
     End Sub
     End Sub
 
 
     '将控件改变路由到设置改变
     '将控件改变路由到设置改变
-    Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckDebugMode.Change, CheckDebugDelay.Change, CheckDebugSkipCopy.Change, CheckUpdateRelease.Change, CheckUpdateSnapshot.Change, CheckHelpChinese.Change, CheckDownloadKeepModpack.Change, CheckDownloadIgnoreQuilt.Change, CheckDownloadCert.Change
+    Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckDebugMode.Change, CheckDebugDelay.Change, CheckDebugSkipCopy.Change, CheckUpdateRelease.Change, CheckUpdateSnapshot.Change, CheckHelpChinese.Change, CheckDownloadIgnoreQuilt.Change, CheckDownloadCert.Change
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked)
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked)
     End Sub
     End Sub
     Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderDebugAnim.Change, SliderDownloadThread.Change, SliderDownloadSpeed.Change
     Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderDebugAnim.Change, SliderDownloadThread.Change, SliderDownloadSpeed.Change
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value)
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value)
     End Sub
     End Sub
-    Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboDownloadVersion.SelectionChanged, ComboDownloadTranslate.SelectionChanged, ComboSystemUpdate.SelectionChanged, ComboSystemActivity.SelectionChanged, ComboDownloadMod.SelectionChanged
+    Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboDownloadVersion.SelectionChanged, ComboModLocalNameStyle.SelectionChanged, ComboDownloadTranslate.SelectionChanged, ComboSystemUpdate.SelectionChanged, ComboSystemActivity.SelectionChanged, ComboDownloadMod.SelectionChanged
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex)
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex)
     End Sub
     End Sub
     Private Shared Sub TextBoxChange(sender As MyTextBox, e As Object) Handles TextSystemCache.ValidatedTextChanged
     Private Shared Sub TextBoxChange(sender As MyTextBox, e As Object) Handles TextSystemCache.ValidatedTextChanged

+ 2 - 0
Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml

@@ -245,6 +245,8 @@
                             <local:MyComboBoxItem Content="OpenBMCLAPI 仪表盘 Lite(作者:Silverteal、Mxmilu666)" />
                             <local:MyComboBoxItem Content="OpenBMCLAPI 仪表盘 Lite(作者:Silverteal、Mxmilu666)" />
                             <local:MyComboBoxItem Content="PCL 主页市场(作者:凌云)" />
                             <local:MyComboBoxItem Content="PCL 主页市场(作者:凌云)" />
                             <local:MyComboBoxItem Content="PCL 更新日志(作者:Joker2184)" />
                             <local:MyComboBoxItem Content="PCL 更新日志(作者:Joker2184)" />
+                            <local:MyComboBoxItem Content="PCL 新功能说明书(作者:WForst-Breeze)" />
+                            <local:MyComboBoxItem Content="OpenMCIM 仪表盘(作者:SALTWOOD)" />
                         </local:MyComboBox>
                         </local:MyComboBox>
                     </Grid>
                     </Grid>
                 </StackPanel>
                 </StackPanel>

+ 10 - 10
Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb

@@ -25,14 +25,15 @@
             Next
             Next
         End If
         End If
 
 
+        AniControlEnabled += 1
+        Reload() '#4826,在每次进入页面时都刷新一下
+        AniControlEnabled -= 1
+
         '非重复加载部分
         '非重复加载部分
         If IsLoaded Then Exit Sub
         If IsLoaded Then Exit Sub
         IsLoaded = True
         IsLoaded = True
 
 
-        AniControlEnabled += 1
         SliderLoad()
         SliderLoad()
-        Reload()
-        AniControlEnabled -= 1
 
 
 #If BETA Then
 #If BETA Then
         PanLauncherHide.Visibility = Visibility.Visible
         PanLauncherHide.Visibility = Visibility.Visible
@@ -269,17 +270,16 @@
         End Try
         End Try
     End Sub
     End Sub
 
 
-
     '顶部栏
     '顶部栏
     Private Sub BtnLogoChange_Click(sender As Object, e As EventArgs) Handles BtnLogoChange.Click
     Private Sub BtnLogoChange_Click(sender As Object, e As EventArgs) Handles BtnLogoChange.Click
-        Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif)|*.png;*.jpg;*.gif", "选择图片")
+        Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp", "选择图片")
         If FileName = "" Then Exit Sub
         If FileName = "" Then Exit Sub
         Try
         Try
             '拷贝文件
             '拷贝文件
             File.Delete(Path & "PCL\Logo.png")
             File.Delete(Path & "PCL\Logo.png")
             CopyFile(FileName, Path & "PCL\Logo.png")
             CopyFile(FileName, Path & "PCL\Logo.png")
             '设置当前显示
             '设置当前显示
-            FrmMain.ImageTitleLogo.Source = New MyBitmap(Path & "PCL\Logo.png")
+            FrmMain.ImageTitleLogo.Source = Path & "PCL\Logo.png"
         Catch ex As Exception
         Catch ex As Exception
             If ex.Message.Contains("参数无效") Then
             If ex.Message.Contains("参数无效") Then
                 Log("改变标题栏图片失败,该图片文件可能并非标准格式。" & vbCrLf &
                 Log("改变标题栏图片失败,该图片文件可能并非标准格式。" & vbCrLf &
@@ -296,7 +296,7 @@ Refresh:
         '已有图片则不再选择
         '已有图片则不再选择
         If File.Exists(Path & "PCL\Logo.png") Then
         If File.Exists(Path & "PCL\Logo.png") Then
             Try
             Try
-                FrmMain.ImageTitleLogo.Source = New MyBitmap(Path & "PCL\Logo.png")
+                FrmMain.ImageTitleLogo.Source = Path & "PCL\Logo.png"
             Catch ex As Exception
             Catch ex As Exception
                 If ex.Message.Contains("参数无效") Then
                 If ex.Message.Contains("参数无效") Then
                     Log("调整标题栏图片失败,该图片文件可能并非标准格式。" & vbCrLf &
                     Log("调整标题栏图片失败,该图片文件可能并非标准格式。" & vbCrLf &
@@ -315,7 +315,7 @@ Refresh:
             Exit Sub
             Exit Sub
         End If
         End If
         '没有图片则要求选择
         '没有图片则要求选择
-        Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif)|*.png;*.jpg;*.gif", "选择图片")
+        Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp", "选择图片")
         If FileName = "" Then
         If FileName = "" Then
             FrmMain.ImageTitleLogo.Source = Nothing
             FrmMain.ImageTitleLogo.Source = Nothing
             e.Handled = True
             e.Handled = True
@@ -428,8 +428,8 @@ Refresh:
         RadioLauncherTheme5Gray.Opacity -= 0.23
         RadioLauncherTheme5Gray.Opacity -= 0.23
         RadioLauncherTheme5.Opacity += 0.23
         RadioLauncherTheme5.Opacity += 0.23
         AniStart({
         AniStart({
-            AaOpacity(RadioLauncherTheme5Gray, 1, 1000),
-            AaOpacity(RadioLauncherTheme5, -1, 1000)
+            AaOpacity(RadioLauncherTheme5Gray, 1, 1000 * AniSpeed),
+            AaOpacity(RadioLauncherTheme5, -1, 1000 * AniSpeed)
         }, "ThemeUnlock")
         }, "ThemeUnlock")
         If RadioLauncherTheme5Gray.Opacity < 0.08 Then
         If RadioLauncherTheme5Gray.Opacity < 0.08 Then
             ThemeUnlock(5, UnlockHint:="隐藏主题 玄素黑 已解锁!")
             ThemeUnlock(5, UnlockHint:="隐藏主题 玄素黑 已解锁!")

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

@@ -61,7 +61,7 @@
             PageChangeRun(PageGet(ID))
             PageChangeRun(PageGet(ID))
             PageID = ID
             PageID = ID
         Catch ex As Exception
         Catch ex As Exception
-            Log(ex, "切换设置分页面失败(ID " & ID & ")", LogLevel.Feedback)
+            Log(ex, "切换分页面失败(ID " & ID & ")", LogLevel.Feedback)
         Finally
         Finally
             AniControlEnabled -= 1
             AniControlEnabled -= 1
         End Try
         End Try

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

@@ -30,7 +30,7 @@
     ''' 刷新 Mod 列表。
     ''' 刷新 Mod 列表。
     ''' </summary>
     ''' </summary>
     Public Sub ReloadModList(Optional ForceReload As Boolean = False)
     Public Sub ReloadModList(Optional ForceReload As Boolean = False)
-        If LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", If(ForceReload, LoaderFolderRunType.ForceRun, LoaderFolderRunType.RunOnUpdated)) Then
+        If LoaderRun(If(ForceReload, LoaderFolderRunType.ForceRun, LoaderFolderRunType.RunOnUpdated)) Then
             Log("[System] 已刷新 Mod 列表")
             Log("[System] 已刷新 Mod 列表")
             Filter = FilterType.All
             Filter = FilterType.All
             PanBack.ScrollToHome()
             PanBack.ScrollToHome()
@@ -43,9 +43,12 @@
     End Sub
     End Sub
     Private Sub Load_Click(sender As Object, e As MouseButtonEventArgs) Handles Load.Click
     Private Sub Load_Click(sender As Object, e As MouseButtonEventArgs) Handles Load.Click
         If McModLoader.State = LoadState.Failed Then
         If McModLoader.State = LoadState.Failed Then
-            LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.ForceRun)
+            LoaderRun(LoaderFolderRunType.ForceRun)
         End If
         End If
     End Sub
     End Sub
+    Public Function LoaderRun(Type As LoaderFolderRunType) As Boolean
+        Return LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", Type)
+    End Function
 
 
 #End Region
 #End Region
 
 
@@ -329,7 +332,7 @@
     Private Sub ChangeAllSelected(Value As Boolean)
     Private Sub ChangeAllSelected(Value As Boolean)
         AniControlEnabled += 1
         AniControlEnabled += 1
         SelectedMods.Clear()
         SelectedMods.Clear()
-        For Each Item As MyLocalModItem In GetShowingMods(True).Select(Function(m) ModItems(m.RawFileName))
+        For Each Item As MyLocalModItem In GetShowingMods(True).Where(Function(m) ModItems.ContainsKey(m.RawFileName)).Select(Function(m) ModItems(m.RawFileName))
             Item.Checked = Value
             Item.Checked = Value
             If Value Then SelectedMods.Add(Item.Entry.RawFileName)
             If Value Then SelectedMods.Add(Item.Entry.RawFileName)
         Next
         Next
@@ -467,10 +470,12 @@
             '更改 Loader 中的列表
             '更改 Loader 中的列表
             Dim NewModEntity As New McMod(NewPath)
             Dim NewModEntity As New McMod(NewPath)
             NewModEntity.FromJson(ModEntity.ToJson)
             NewModEntity.FromJson(ModEntity.ToJson)
-            Dim IndexOfLoader As Integer = McModLoader.Output.IndexOf(ModEntity)
-            McModLoader.Output.RemoveAt(IndexOfLoader)
-            McModLoader.Output.Insert(IndexOfLoader, NewModEntity)
-            If SearchResult IsNot Nothing Then
+            If McModLoader.Output.Contains(ModEntity) Then
+                Dim IndexOfLoader As Integer = McModLoader.Output.IndexOf(ModEntity)
+                McModLoader.Output.RemoveAt(IndexOfLoader)
+                McModLoader.Output.Insert(IndexOfLoader, NewModEntity)
+            End If
+            If SearchResult IsNot Nothing AndAlso SearchResult.Contains(ModEntity) Then '#4862
                 Dim IndexOfResult As Integer = SearchResult.IndexOf(ModEntity)
                 Dim IndexOfResult As Integer = SearchResult.IndexOf(ModEntity)
                 SearchResult.Remove(ModEntity)
                 SearchResult.Remove(ModEntity)
                 SearchResult.Insert(IndexOfResult, NewModEntity)
                 SearchResult.Insert(IndexOfResult, NewModEntity)
@@ -489,6 +494,7 @@
             Hint("由于文件被占用,Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
             Hint("由于文件被占用,Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
             ReloadModList(True)
             ReloadModList(True)
         End If
         End If
+        LoaderRun(LoaderFolderRunType.UpdateOnly)
     End Sub
     End Sub
 
 
     '更新
     '更新
@@ -696,6 +702,7 @@
             Log(ex, "删除 Mod 出现未知错误", LogLevel.Feedback)
             Log(ex, "删除 Mod 出现未知错误", LogLevel.Feedback)
             ReloadModList(True)
             ReloadModList(True)
         End Try
         End Try
+        LoaderRun(LoaderFolderRunType.UpdateOnly)
     End Sub
     End Sub
 
 
     '取消选择
     '取消选择

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

@@ -186,12 +186,15 @@
     <Compile Include="Controls\MyComboBox.vb" />
     <Compile Include="Controls\MyComboBox.vb" />
     <Compile Include="Controls\MyComboBoxItem.vb" />
     <Compile Include="Controls\MyComboBoxItem.vb" />
     <Compile Include="Controls\MyCard.vb" />
     <Compile Include="Controls\MyCard.vb" />
+    <Compile Include="Controls\MyImage.vb" />
     <Compile Include="Controls\MyPageRight.vb" />
     <Compile Include="Controls\MyPageRight.vb" />
     <Compile Include="Controls\MyPageLeft.vb" />
     <Compile Include="Controls\MyPageLeft.vb" />
     <Compile Include="Controls\MyIconTextButton.xaml.vb">
     <Compile Include="Controls\MyIconTextButton.xaml.vb">
       <DependentUpon>MyIconTextButton.xaml</DependentUpon>
       <DependentUpon>MyIconTextButton.xaml</DependentUpon>
     </Compile>
     </Compile>
-    <Compile Include="Controls\MyImage.vb" />
+
+
+
     <Compile Include="Modules\Minecraft\ModComp.vb" />
     <Compile Include="Modules\Minecraft\ModComp.vb" />
     <Compile Include="Modules\Minecraft\ModJava.vb" />
     <Compile Include="Modules\Minecraft\ModJava.vb" />
     <Compile Include="Modules\Minecraft\ModMod.vb" />
     <Compile Include="Modules\Minecraft\ModMod.vb" />
@@ -476,6 +479,7 @@
       <DependentUpon>FormMain.xaml</DependentUpon>
       <DependentUpon>FormMain.xaml</DependentUpon>
       <SubType>Code</SubType>
       <SubType>Code</SubType>
     </Compile>
     </Compile>
+
     <Page Include="Modules\Minecraft\MyLocalModItem.xaml">
     <Page Include="Modules\Minecraft\MyLocalModItem.xaml">
       <Generator>MSBuild:Compile</Generator>
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
       <SubType>Designer</SubType>
@@ -910,6 +914,7 @@
     <Resource Include="Images\Heads\MCBBS.png" />
     <Resource Include="Images\Heads\MCBBS.png" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
+
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <Resource Include="Images\Blocks\NeoForge.png" />
     <Resource Include="Images\Blocks\NeoForge.png" />

+ 155 - 154
Plain Craft Launcher 2/Resources/Custom.xaml

@@ -4,311 +4,309 @@
      你也可以使用 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 限制它的高度。" />
-		<Image Height="50" HorizontalAlignment="Center" Source="https://zh.minecraft.wiki/images/Grass_Block_JE7_BE6.png?bf67e" />
-	</StackPanel>
+                    Text="local:MyImage 代表图片,你需要在它的 Source 属性中填写一个网址或文件路径,它会从该处获取图片并显示。通常需要使用 Height 限制它的高度。" />
+        <local:MyImage Height="50" HorizontalAlignment="Center" Source="https://www.baidu.com/img/flexible/logo/pc/result.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="打开 Minecraft Wiki" EventType="打开网页" EventData="https://zh.minecraft.wiki/" />
                     Text="打开 Minecraft Wiki" EventType="打开网页" EventData="https://zh.minecraft.wiki/" />
         <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.20.1 并进入 Hypixel" EventType="启动游戏" EventData="1.20.1|mc.hypixel.net" ToolTip="服务器 IP:mc.hypixel.net" />
                     Text="启动 1.20.1 并进入 Hypixel" EventType="启动游戏" EventData="1.20.1|mc.hypixel.net" ToolTip="服务器 IP:mc.hypixel.net" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="在 EventData 中填写 \current,表示启动当前被选中的游戏版本,同样可以在后面填写服务器 IP。" />
                     Text="在 EventData 中填写 \current,表示启动当前被选中的游戏版本,同样可以在后面填写服务器 IP。" />
-		<local:MyButton Margin="0,4,0,10" Width="250" Height="35"
+        <local:MyButton Margin="0,4,0,10" Width="250" Height="35"
                     Text="启动当前选中版本并进入 Hypixel" EventType="启动游戏" EventData="\current|mc.hypixel.net" ToolTip="启动当前版本" />
                     Text="启动当前选中版本并进入 Hypixel" EventType="启动游戏" EventData="\current|mc.hypixel.net" ToolTip="启动当前版本" />
-	</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"
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="此外,PCL 会将文件中的 “花括号 path” 替换为 PCL 可执行文件所在文件夹({path}),可以用于执行特定程序或加载图片。" />
                     Text="此外,PCL 会将文件中的 “花括号 path” 替换为 PCL 可执行文件所在文件夹({path}),可以用于执行特定程序或加载图片。" />
-		<local:MyButton Margin="0,4,0,10" Width="200" Height="35"
+        <local:MyButton Margin="0,4,0,10" Width="200" Height="35"
                     Text="打开 PCL 日志" EventType="打开文件" EventData="Log1.txt" ToolTip="PCL 的日志文件就在 PCL 文件夹内,且名为 Log1.txt" />
                     Text="打开 PCL 日志" EventType="打开文件" EventData="Log1.txt" ToolTip="PCL 的日志文件就在 PCL 文件夹内,且名为 Log1.txt" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+        <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"
+        <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;点击按钮会打开文件管理器,并且借助参数要求它选中这个文件。" />
                     Text="定位游戏启动脚本" EventType="打开文件" EventData="explorer.exe|/select,LatestLaunch.bat" ToolTip="PCL 会将上一次启动 MC 的脚本保存在 PCL 文件夹内,且名为 LatestLaunch.bat。&#xA;点击按钮会打开文件管理器,并且借助参数要求它选中这个文件。" />
-	</StackPanel>
+    </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">
-	<StackPanel Margin="25,40,23,15">
-		<TextBlock Margin="0,4,0,6" FontWeight="Bold"
+    <StackPanel Margin="25,40,23,15">
+        <TextBlock Margin="0,4,0,6" FontWeight="Bold"
                     Text="EventType 共有以下种类:" />
                     Text="EventType 共有以下种类:" />
-		<TextBlock Margin="0,0,0,4"
+        <TextBlock Margin="0,0,0,4"
                     Text="· 打开网页、启动游戏、打开文件:已在上方的卡片中介绍" />
                     Text="· 打开网页、启动游戏、打开文件:已在上方的卡片中介绍" />
-		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
                     Text="查看 PCL 源代码" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2/blob/main/Plain%20Craft%20Launcher%202/FormMain.xaml.vb"/>
                     Text="查看 PCL 源代码" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2/blob/main/Plain%20Craft%20Launcher%202/FormMain.xaml.vb"/>
-		<TextBlock Margin="0,0,0,4"
+        <TextBlock Margin="0,0,0,4"
                     Text="· 今日人品、清理垃圾、内存优化、安装整合包:触发对应功能" />
                     Text="· 今日人品、清理垃圾、内存优化、安装整合包:触发对应功能" />
-		<TextBlock Margin="0,0,0,4"
+        <TextBlock Margin="0,0,0,4"
                     Text="· 复制文本:将 EventData 中的内容复制到剪贴板" />
                     Text="· 复制文本:将 EventData 中的内容复制到剪贴板" />
-		<TextBlock Margin="0,0,0,4"
+        <TextBlock Margin="0,0,0,4"
                     Text="· 刷新主页:点击重新加载主页。如果主页文件为联网更新,这会重新下载主页内容" />
                     Text="· 刷新主页:点击重新加载主页。如果主页文件为联网更新,这会重新下载主页内容" />
-		<TextBlock Margin="0,0,0,4"
-                    Text="· 下载文件:在点击时要求选择文件保存位置,并且下载 EventData 的链接中的文件" />
-		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+        <TextBlock Margin="0,0,0,4"
+                    Text="· 下载文件:在点击时要求选择文件保存位置,并且下载 EventData 的链接中的文件&#xa;       EventData 中也能可选地指定文件名或目标文件夹,格式为:链接|文件名|文件夹" />
+        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
                     Text="下载百度的 Logo" EventType="下载文件" EventData="https://www.baidu.com/img/flexible/logo/pc/result.png" />
                     Text="下载百度的 Logo" EventType="下载文件" EventData="https://www.baidu.com/img/flexible/logo/pc/result.png" />
-		<TextBlock Margin="0,4,0,4"
+        <TextBlock Margin="0,4,0,4"
                     Text="· 打开帮助:跳转到 EventData 对应的帮助文件(输入相对 Help 文件夹的路径)"/>
                     Text="· 打开帮助:跳转到 EventData 对应的帮助文件(输入相对 Help 文件夹的路径)"/>
-		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
                     Text="打开自定义帮助页面" EventType="打开帮助" EventData="帮助/自定义帮助.json"/>
                     Text="打开自定义帮助页面" EventType="打开帮助" EventData="帮助/自定义帮助.json"/>
-		<TextBlock Margin="0,4,0,4"
-                    Text="· 切换页面:强制切换到 PCL 的某一页面(尝试切换到不太对头的页面会出现不可预知的 Bug,使用时请谨慎)&#xa;       EventData 为 页面种类|次要页面种类(PageType|PageSubType,例如 1|1)&#xa;       点击上方的 查看 PCL 源代码 按钮,在 1000 行左右的位置即可查找到对应数值"/>
-		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+        <TextBlock Margin="0,4,0,4"
+                    Text="· 切换页面:强制切换到 PCL 的某一页面(尝试切换到不太对头的页面会出现不可预知的 Bug)&#xa;       EventData 为 页面种类|次要页面种类(PageType|PageSubType,例如 1|1)&#xa;       点击上方的 查看 PCL 源代码 按钮,在 1000 行左右的位置即可查找到对应数值"/>
+        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
                     Text="切换到下载页面" EventType="切换页面" EventData="1|1"/>
                     Text="切换到下载页面" EventType="切换页面" EventData="1|1"/>
-		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
                     Text="切换到启动器设置" EventType="切换页面" EventData="3|2"/>
                     Text="切换到启动器设置" EventType="切换页面" EventData="3|2"/>
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="StackPanel 横向布局" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <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"
+    <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="notepad" />
                         Text="打开记事本" EventType="打开文件" EventData="notepad" />
-		</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="下载百度的 Logo" EventType="下载文件" EventData="https://www.baidu.com/img/flexible/logo/pc/result.png" />
                         Text="下载百度的 Logo" EventType="下载文件" EventData="https://www.baidu.com/img/flexible/logo/pc/result.png" />
-		</StackPanel>
-	</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="百度一下,你就知道!"
-		            EventType="打开网页" EventData="https://www.baidu.com/" Type="Clickable" />
+        <local:MyListItem  Margin="-5,2,-5,8"
+                    Logo="https://www.baidu.com/favicon.ico" Title="百度" Info="百度一下,你就知道!"
+                    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"/>
-			<!-- 布料(Fabric 图标) -->
-			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Fabric.png"/>
-			<!-- 狐狸(NeoForge 图标) -->
-			<Image Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/NeoForge.png"/>
-		</StackPanel>
+        <StackPanel Orientation="Horizontal" Margin="10,8,0,15" Height="30">
+            <!-- 命令方块 -->
+            <local:MyImage Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/CommandBlock.png"/>
+            <!-- 圆石 -->
+            <local:MyImage Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Cobblestone.png"/>
+            <!-- 金块 -->
+            <local:MyImage Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/GoldBlock.png"/>
+            <!-- 草方块 -->
+            <local:MyImage Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Grass.png"/>
+            <!-- 草径 -->
+            <local:MyImage Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/GrassPath.png"/>
+            <!-- 铁砧 -->
+            <local:MyImage Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Anvil.png"/>
+            <!-- 红石块 -->
+            <local:MyImage Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/RedstoneBlock.png"/>
+            <!-- 红石灯(亮) -->
+            <local:MyImage Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/RedstoneLampOn.png"/>
+            <!-- 红石灯(灭) -->
+            <local:MyImage Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/RedstoneLampOff.png"/>
+            <!-- 鸡蛋 -->
+            <local:MyImage Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Egg.png"/>
+            <!-- 布料(Fabric 图标) -->
+            <local:MyImage Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/Fabric.png"/>
+            <!-- 狐狸(NeoForge 图标) -->
+            <local:MyImage Width="30" Margin="5,0,0,0" Source="pack://application:,,,/images/Blocks/NeoForge.png"/>
+        </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 TextWrapping="Wrap" Margin="0,15,0,8" Foreground="{DynamicResource ColorBrush2}" Background="{DynamicResource ColorBrush6}"
                     Text="除了字体颜色,你也可以将主题色用于背景色、边框颜色等颜色参数。例如这行字的颜色为浓度 2,背景为浓度 6。" />
                     Text="除了字体颜色,你也可以将主题色用于背景色、边框颜色等颜色参数。例如这行字的颜色为浓度 2,背景为浓度 6。" />
-	</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">
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+    <StackPanel Margin="25,40,23,15">
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="在花括号 {} 中写入特定内容,PCL 会在加载时对其进行替换。&#xa;例如,path 会被替换为 PCL 可执行文件所在文件夹({path}),用于在 EventData 中指定特定文件,或是加载图片。" />
                     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"
+        <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 则是 “你知道吗” 的内容。" />
                     Text="你也可以使用替换标记来获取随机内容:cave 会被替换为一条随机的回声洞内容,hint 则是 “你知道吗” 的内容。" />
-		<TextBlock TextWrapping="Wrap" Margin="10,6,0,5" Foreground="{DynamicResource ColorBrush3}"
+        <TextBlock TextWrapping="Wrap" Margin="10,6,0,5" Foreground="{DynamicResource ColorBrush3}"
                     Text="你知道吗:{hint}" />
                     Text="你知道吗:{hint}" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="进阶:Grid 布局" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <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"
+    <StackPanel Margin="25,40,23,15">
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="如果要实现更复杂的布局,则必须使用 Grid。Grid 可以让按钮们自动适应窗口宽度:随意拉伸 PCL 窗口,按钮大小会自动改变。" />
                     Text="如果要实现更复杂的布局,则必须使用 Grid。Grid 可以让按钮们自动适应窗口宽度:随意拉伸 PCL 窗口,按钮大小会自动改变。" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
                     Text="你可以上网查找 “WPF Grid” 的相关教程,这里仅给出一个例子,不作详细介绍。你可以仅在这个教学卡片的基础上稍作调整,来实现自己的按钮布局。" />
                     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"
+        <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/" />
                         Text="打开 B 站" EventType="打开网页" EventData="https://www.bilibili.com/" />
-			<local:MyButton Grid.Column="1" Margin="0,0,10,0" Height="35"
+            <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" />
                         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"
+            <local:MyButton Grid.Column="2" Margin="0,0,10,0" Height="35"
                         Text="打开记事本" EventType="打开文件" EventData="notepad" />
                         Text="打开记事本" EventType="打开文件" EventData="notepad" />
-		</Grid>
-	</StackPanel>
+        </Grid>
+    </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 Margin="0,0,0,10" Text="以下特性于 PCL 2.6.5 (299) 加入,不适用于更早版本的 PCL。" IsWarn="False" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+    <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 属性还可以调整图标的缩放比例。" />
                     Text="MyIconTextButton 是一种带有自定义矢量图标的按钮变体,它同样支持 EventType 与 EventData 属性。&#xa;通过设置 Logo 属性来控制它的图标,设置 LogoScale 属性还可以调整图标的缩放比例。" />
-		<local:MyIconTextButton Margin="0,4,0,8" Height="35" HorizontalAlignment="Center"
+        <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"
                     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" />
                     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"
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="Logo 属性的内容是 SVG Path 的值,你需要使用 SVG 编辑器或者从图标资源网站(如 IconFont)查找它。不要自己写,写不懂的。&#xa;此外,它还有两种 ColorType:Black(默认)和 Highlight。" />
                     Text="Logo 属性的内容是 SVG Path 的值,你需要使用 SVG 编辑器或者从图标资源网站(如 IconFont)查找它。不要自己写,写不懂的。&#xa;此外,它还有两种 ColorType:Black(默认)和 Highlight。" />
-		<local:MyIconTextButton Margin="0,4,0,8" Height="35" HorizontalAlignment="Center"
+        <local:MyIconTextButton Margin="0,4,0,8" Height="35" HorizontalAlignment="Center"
                     ColorType="Highlight" Text="打开 IconFont" EventType="打开网页" EventData="https://www.iconfont.cn/"
                     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" />
                     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"
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="如果只想要图标,不要文字,还可以使用 MyIconButton。它不能设置 Text 属性,只有一个图标可以按。&#xa;它的 Theme 属性可以设置为 Color(默认)、White、Black、Red。" />
                     Text="如果只想要图标,不要文字,还可以使用 MyIconButton。它不能设置 Text 属性,只有一个图标可以按。&#xa;它的 Theme 属性可以设置为 Color(默认)、White、Black、Red。" />
-		<local:MyIconButton Margin="0,4,0,0" Width="25" Height="25" HorizontalAlignment="Center"
+        <local:MyIconButton Margin="0,4,0,0" Width="25" Height="25" HorizontalAlignment="Center"
                     Theme="Black" EventType="弹出窗口" EventData="事件|MyIconButton 当然也支持 EventType 和 EventData——要不然介绍它干啥?"
                     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" />
                     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"
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="如果只想显示一个图标,不想让它是能按的按钮,可以使用 WPF 自带的 Path 等绘图控件,感兴趣的话可以自行搜索。想偷懒也可以把 MyIconButton 的 IsHitTestVisible 属性改为 False……" />
                     Text="如果只想显示一个图标,不想让它是能按的按钮,可以使用 WPF 自带的 Path 等绘图控件,感兴趣的话可以自行搜索。想偷懒也可以把 MyIconButton 的 IsHitTestVisible 属性改为 False……" />
-		<Path Margin="0,4,0,0" Width="20" Height="20" HorizontalAlignment="Center"
+        <Path Margin="0,4,0,0" Width="20" Height="20" HorizontalAlignment="Center"
                     Stretch="Uniform" Fill="{DynamicResource ColorBrush1}"
                     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" />
                     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>
+    </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 Margin="0,0,0,10" Text="以下特性于 PCL 2.6.5 (299) 加入,不适用于更早版本的 PCL。" IsWarn="False" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
+    <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 版本。" />
                     Text="如果你在尝试制作联网更新的自定义主页,可以查看下方的 GitHub 讨论页。&#xa;你可以根据其中的介绍为主页添加版本号检查以节省流量,也可以通过检查 Referer 和 User Agent 来确定对方的 PCL 版本。" />
-		<local:MyButton Margin="0,0,0,8" Height="35" HorizontalAlignment="Center" Padding="20,0,20,0"
+        <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" />
                     Text="打开 GitHub 讨论页" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2/discussions/2528" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 </local:MyCard>
 
 
 <!-- 
 <!-- 
@@ -317,9 +315,9 @@
  - Width、Height:设置宽度与高度
  - Width、Height:设置宽度与高度
  - ToolTip:设置鼠标指向时的提示文本
  - ToolTip:设置鼠标指向时的提示文本
  - 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 为否
+ - HorizontalAlignment:设置横向对齐方式;居左:Left、居中:Center、居右:Right、拉伸(默认):Stretch
+ - VerticalAlignment:设置纵向对齐方式;居上:Top、居中:Center、居下:Bottom、拉伸(默认):Stretch
+ - IsHitTestVisible:该控件是否响应交互(点击、指向动画等);True 为是(默认),False 为否
 
 
 TextBlock(纯文本):
 TextBlock(纯文本):
  - Text:设置显示的文本(如果要使用引号等特殊字符,自行百度 XAML 转义)
  - Text:设置显示的文本(如果要使用引号等特殊字符,自行百度 XAML 转义)
@@ -328,17 +326,20 @@ TextBlock(纯文本):
  - FontWeight:设置为 Bold 时,文字加粗
  - FontWeight:设置为 Bold 时,文字加粗
  - Foreground:调整前景颜色(十六进制字符串,例如 #FF010203 代表 ARGB 中的 255,1,2,3。前两位代表不透明度,FF 为全不透明,00 为全透明)
  - Foreground:调整前景颜色(十六进制字符串,例如 #FF010203 代表 ARGB 中的 255,1,2,3。前两位代表不透明度,FF 为全不透明,00 为全透明)
 
 
-Image(图片):
+local:MyImage(图片):
  - Source:要显示的图片的网址,或 PCL 内置图片位置
  - Source:要显示的图片的网址,或 PCL 内置图片位置
+ - FallbackSource:当 Source 首次下载失败时,会从该备用地址加载图片
+ - LoadingSource:正在下载网络图片时显示的本地图片(不支持联网下载);默认为 pack://application:,,,/images/Icons/NoIcon.png
+ - EnableCache:若图片是联网获取的,是否将图片保存到本地缓存,在 7 天内不再重新下载;True 为是(默认),False 为否
 
 
 local:MyCard(卡片):
 local:MyCard(卡片):
  - Title:设置显示的标题文本
  - Title:设置显示的标题文本
  - CanSwap:卡片是否可以折叠,True 为是,False 为否
  - CanSwap:卡片是否可以折叠,True 为是,False 为否
  - IsSwaped:卡片是否默认折叠,要求 CanSwap 必须为 True
  - IsSwaped:卡片是否默认折叠,要求 CanSwap 必须为 True
  - HorizontalAlignment:若使用,要求 CanSwap 必须为 False
  - HorizontalAlignment:若使用,要求 CanSwap 必须为 False
- - UseAnimation:是否在展开等高度改变时触发动画,True 为是(默认),False 为否
- - SwapLogoRight:卡片折叠时的箭头是朝下还是朝右,True 为朝右,False 为朝下(默认)
- - HasMouseAnimation:是否在鼠标指向时改变卡片颜色,True 为是(默认),False 为否
+ - UseAnimation:是否在展开等高度改变时触发动画;True 为是(默认),False 为否
+ - SwapLogoRight:卡片折叠时的箭头是朝下还是朝右;True 为朝右,False 为朝下(默认)
+ - HasMouseAnimation:是否在鼠标指向时改变卡片颜色;True 为是(默认),False 为否
 
 
 local:MyHint(提示条):
 local:MyHint(提示条):
  - Text:设置显示的文本(&#xA; 代表换行)
  - Text:设置显示的文本(&#xA; 代表换行)

二進制
Plain Craft Launcher 2/Resources/Help.zip


文件差異過大導致無法顯示
+ 116 - 116
Plain Craft Launcher 2/Resources/ModData.txt


部分文件因文件數量過多而無法顯示