龙腾猫跃 2 лет назад
Родитель
Сommit
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. BIN
      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] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}")
             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()}")
             '检测异常环境
             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
                     Log("[Start] 加载 DLL: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
                 Return AssemblyImazenWebp
             End SyncLock

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

@@ -1,7 +1,7 @@
-Public Class MyImage
+Public Class MyImage
     Inherits Image
 
-#Region "辅助属性注册"
+#Region "公开属性"
 
     ''' <summary>
     ''' 网络图片的缓存有效期。
@@ -22,14 +22,6 @@ Public Class MyImage
     End Property
     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>
     ''' 与 Image 的 Source 类似。
     ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示,图片会保存为本地缓存。
@@ -43,74 +35,150 @@ Public Class MyImage
             If value = "" Then value = Nothing
             If _Source = value Then Exit Property
             _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
-                '空
-                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
-                Log(ex, $"加载图片失败({value})", LogLevel.Hint)
+                Log(ex, $"加载图片失败({value})")
                 Try
-                    File.Delete(TempPath) '删除缓存,以免缓存出现问题导致一直加载失败
+                    If value.StartsWithF(PathTemp) AndAlso File.Exists(value) Then File.Delete(value)
                 Catch
                 End Try
             End Try
         End Set
     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 _Logo.StartsWithF("http", True) Then
                     '网络图片
-                    PathLogo = New Image With {
+                    PathLogo = New MyImage With {
                             .Tag = Me,
                             .IsHitTestVisible = LogoClickable,
-                            .Source = New ImageSourceConverter().ConvertFromString(_Logo),
+                            .Source = _Logo,
                             .RenderTransformOrigin = New Point(0.5, 0.5),
                             .RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale},
                             .SnapsToDevicePixels = True, .UseLayoutRounding = False}
                     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 {
                             .Tag = Me,
                             .IsHitTestVisible = LogoClickable,
-                            .Background = Bitmap,
+                            .Background = New MyBitmap(_Logo),
                             .RenderTransformOrigin = New Point(0.5, 0.5),
                             .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
                     '矢量图
                     PathLogo = New Shapes.Path With {
@@ -327,8 +320,8 @@
     Private Sub OnSizeChanged() Handles Me.SizeChanged
         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))
-        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)
             Else
                 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 Property '内容
     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 Property '内容
     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
         White
         Highlight

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

@@ -90,21 +90,21 @@
         Get
             Return GetValue(EventTypeProperty)
         End Get
-
         Set(value As String)
             SetValue(EventTypeProperty, value)
         End Set
     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
         Get
             Return GetValue(EventDataProperty)
         End Get
-
         Set(value As String)
             SetValue(EventDataProperty, value)
         End Set
     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

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

@@ -104,7 +104,7 @@
                             <GradientStop Color="{StaticResource ColorObject3}" Offset="1"/>
                         </LinearGradientBrush>
                     </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" 
                                        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" 
@@ -117,7 +117,7 @@
                         </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" />
                         <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">
                             <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" />

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

@@ -69,84 +69,19 @@ Public Class FormMain
             FeatureCount += 10
             BugCount += 10
         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
         '5:          FEAT+
         '4:     IMP+ FEAT*
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '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 = 337 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复数个与新正版登录相关的严重 Bug"))
             FeatureCount += 3
@@ -233,117 +168,6 @@ Public Class FormMain
             FeatureCount += 10
             BugCount += 10
         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
         '整理更新日志文本
         Dim ContentList As New List(Of String)
@@ -386,9 +210,6 @@ Public Class FormMain
             '触发降级
             DowngradeSub(LastVersion)
         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)
         Setup.Load("UiLauncherTheme")
@@ -451,12 +272,12 @@ Public Class FormMain
         '加载窗口
         ThemeRefreshMain()
         Try
-            Height = ReadReg("WindowHeight", MinHeight + 50)
-            Width = ReadReg("WindowWidth", MinWidth + 50)
+            Height = Setup.Get("WindowHeight")
+            Width = Setup.Get("WindowWidth")
         Catch ex As Exception '修复 #2019
             Log(ex, "读取窗口默认大小失败", LogLevel.Hint)
-            Height = MinHeight + 50
-            Width = MinWidth + 50
+            Height = MinHeight + 100
+            Width = MinWidth + 100
         End Try
 #If DEBUG Then
         MinHeight = 50
@@ -595,6 +416,16 @@ Public Class FormMain
             Setup.Set("UiHiddenOtherHelp", False)
             Log("[Start] 已解除帮助页面的隐藏")
         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 LowerVersionCode >= VersionCode Then Exit Sub
@@ -636,35 +467,35 @@ Public Class FormMain
         End If
         '关闭
         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
     Private Shared IsLogShown As Boolean = False
     Public Shared Sub EndProgramForce(Optional ReturnCode As Result = Result.Success)
@@ -709,8 +540,8 @@ Public Class FormMain
     Public IsSizeSaveable As Boolean = False
     Private Sub FormMain_SizeChanged() Handles Me.SizeChanged, Me.Loaded
         If IsSizeSaveable Then
-            WriteReg("WindowHeight", Height)
-            WriteReg("WindowWidth", Width)
+            Setup.Set("WindowHeight", Height)
+            Setup.Set("WindowWidth", Width)
         End If
         RectForm.Rect = New Rect(0, 0, BorderForm.ActualWidth, BorderForm.ActualHeight)
         PanForm.Width = BorderForm.ActualWidth + 0.001
@@ -921,11 +752,12 @@ Public Class FormMain
                     End If
                 End If
                 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
             End If
             '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 Th As Thread, ThreadInfo As NetThread
                 SyncLock LockChain
-
                     '获取线程起点与下载源
                     '不分割
                     If IsNoSplit Then GoTo Capture
@@ -1049,6 +1048,15 @@ StartThread:
                     If ModeDebug AndAlso HttpResponse.ResponseUri.OriginalString <> Info.Source.Url Then
                         Log($"[Download] {LocalName} {Info.Uuid}#:重定向至 {HttpResponse.ResponseUri.OriginalString}")
                     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
                     If ContentLength = -1 Then
@@ -1133,7 +1141,7 @@ NotSupportRange:
                                 If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount
                             End SyncLock
                             Dim DeltaTime = GetTimeTick() - Info.LastReceiveTime
-                            If DeltaTime > 1000000 Then DeltaTime = 0 '时间刻反转导致出现极大值
+                            If DeltaTime > 1000000 Then DeltaTime = 1 '时间刻反转导致出现极大值
                             If RealDataCount > 0 Then
                                 '有数据
                                 If Info.DownloadDone = 0 Then
@@ -1174,7 +1182,7 @@ NotSupportRange:
                                     ResultStream.Write(HttpData, 0, RealDataCount)
                                 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。")
                                 End If
                                 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()
             End Try
         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>

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

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

@@ -389,7 +389,7 @@ Extracted:
         版本Json中存在多个Forge
         Mod过多导致超出ID限制
         NightConfig的Bug
-        ShadersMod与Optifine同时安装
+        ShadersMod与OptiFine同时安装
         Forge安装不完整
         Mod需要Java11
         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("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.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("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))
@@ -525,6 +525,7 @@ Done:
             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("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("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)
@@ -623,8 +624,11 @@ Done:
         '崩溃报告分析
         If LogCrash IsNot Nothing 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
 
@@ -744,7 +748,7 @@ NextStack:
             '[Fabric] 获取所有包含 Mod 信息的行
             Dim ModNameLines As New List(Of String)
             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)
             Next
             Log("[Crash] 崩溃报告中找到 " & ModNameLines.Count & " 个可能的 Mod 项目行")
@@ -886,6 +890,7 @@ NextStack:
                             WriteFile(TempFolder & "Report\" & FileName,
                                       SecretFilter(ReadFile(OutputFile, FileEncoding), If(FileName = "启动脚本.bat", "F", "*")),
                                       Encoding:=FileEncoding)
+                            Log($"[Crash] 导出文件:{FileName},编码:{FileEncoding.HeaderName}")
                         End If
                     Next
                     '导出报告
@@ -942,9 +947,9 @@ NextStack:
                     End If
                 Case CrashReason.Mod缺少前置或MC版本错误
                     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
-                        Results.Add("由于未满足 Mod 的依赖项,导致游戏退出。\n请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h")
+                        Results.Add("由于未安装正确的前置 Mod,导致游戏退出。\n请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h")
                     End If
                 Case CrashReason.堆栈分析发现关键字
                     If Additional.Count = 1 Then
@@ -980,7 +985,7 @@ NextStack:
                     If Additional.Count = 1 Then
                         Results.Add("名为 " & Additional.First & " 的 Mod 初始化失败,导致游戏无法继续加载。\n你可以尝试禁用此 Mod,然后观察游戏是否还会崩溃。\n\e\h")
                     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
                 Case CrashReason.特定方块导致崩溃
                     If Additional.Count = 1 Then
@@ -992,7 +997,7 @@ NextStack:
                     If Additional.Count >= 2 Then
                         Results.Add("你重复安装了多个相同的 Mod:\n - " & Join(Additional, "\n - ") & "\n\n每个 Mod 只能出现一次,请删除重复的 Mod,然后再启动游戏。")
                     Else
-                        Results.Add("你可能重复安装了多个相同的 Mod,导致游戏无法继续加载。\n\n每个 Mod 只能出现一次,请删除重复的 Mod,然后再启动游戏。\e\h")
+                        Results.Add("你可能重复安装了多个相同的 Mod,导致游戏出错。\n\n每个 Mod 只能出现一次,请删除重复的 Mod,然后再启动游戏。\e\h")
                     End If
                 Case CrashReason.特定实体导致崩溃
                     If Additional.Count = 1 Then
@@ -1002,12 +1007,12 @@ NextStack:
                     End If
                 Case CrashReason.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不兼容
                     Results.Add("由于低版本 Forge 与当前 Java 不兼容,导致了游戏崩溃。\n\n请尝试以下解决方案:\n - 更新 Forge 到 36.2.26 或更高版本\n - 换用版本低于 1.8.0.320 的 Java")
                 Case CrashReason.版本Json中存在多个Forge
-                    Results.Add("可能由于使用其他启动器修改了 Forge 版本,当前版本的文件存在异常,导致了游戏崩溃。\n请尝试重新全新安装 Forge,而非使用其他启动器修改 Forge 版本。")
+                    Results.Add("可能由于其他启动器修改了 Forge 版本,当前版本的文件存在异常,导致了游戏崩溃。\n请尝试重新全新安装 Forge,而非使用其他启动器修改 Forge 版本。")
                 Case CrashReason.玩家手动触发调试崩溃
                     Results.Add("* 事实上,你的游戏没有任何问题,这是你自己触发的崩溃。\n* 你难道没有更重要的事要做吗?")
                 Case CrashReason.Mod需要Java11
@@ -1033,7 +1038,7 @@ NextStack:
                 Case CrashReason.文件或内容校验失败
                     Results.Add("部分文件或内容校验失败,导致游戏出现了问题。\n\n请尝试删除游戏(包括 Mod)并重新下载,或尝试在重新下载时使用 VPN。\h")
                 Case CrashReason.Forge安装不完整
-                    Results.Add("由于 Forge 安装不完整,导致游戏无法正常运行。\n请尝试重新安装 Forge。\h")
+                    Results.Add("由于安装的 Forge 文件丢失,导致游戏无法正常运行。\n请重新安装一次相同版本的 Forge,然后再启动游戏。\n在打包游戏时删除 libraries 文件夹可能导致此错误。\h")
                 Case CrashReason.Fabric报错
                     If Additional.Count = 1 Then
                         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")
             Case McLoginType.Ms
-                If Setup.Get("CacheMsName") <> "" Then Return Setup.Get("CacheMsName")
+                If Setup.Get("CacheMsV2Name") <> "" Then Return Setup.Get("CacheMsV2Name")
             Case McLoginType.Legacy
                 If Setup.Get("LoginLegacyName") <> "" Then Return Setup.Get("LoginLegacyName").ToString.Before("¨")
             Case McLoginType.Nide
@@ -387,7 +387,7 @@ NextInner:
                 If Setup.Get("CacheAuthName") <> "" Then Return Setup.Get("CacheAuthName")
         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("CacheAuthName") <> "" Then Return Setup.Get("CacheAuthName")
         If Setup.Get("LoginLegacyName") <> "" Then Return Setup.Get("LoginLegacyName").ToString.Before("¨")
@@ -399,7 +399,7 @@ NextInner:
     Public Function McLoginAble() As String
         Select Case Setup.Get("LoginType")
             Case McLoginType.Ms
-                If Setup.Get("CacheMsOAuthRefresh") = "" Then
+                If Setup.Get("CacheMsV2OAuthRefresh") = "" Then
                     Return FrmLoginMs.IsVaild()
                 Else
                     Return ""
@@ -450,7 +450,7 @@ NextInner:
                 Case McLoginType.Legacy
                     LoginData = PageLoginLegacy.GetLoginData()
                 Case McLoginType.Ms
-                    If Setup.Get("CacheMsOAuthRefresh") = "" Then
+                    If Setup.Get("CacheMsV2OAuthRefresh") = "" Then
                         LoginData = PageLoginMs.GetLoginData()
                     Else
                         LoginData = PageLoginMsSkin.GetLoginData()
@@ -552,11 +552,11 @@ Relogin:
         Dim Result = MsLoginStep6(AccessToken)
         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"))
         MsJson.Remove(Input.UserName) '如果更改了玩家名……
         MsJson(Result(1)) = OAuthRefreshToken
@@ -850,7 +850,7 @@ Retry:
         While Converter.Result Is Nothing
             Thread.Sleep(100)
         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},设置完毕后再登录。",
                 "需要使用密码登录", "重新登录", "设置密码", "取消",
                 Button2Action:=Sub() OpenWebsite("https://account.live.com/password/Change")) = 1 Then
@@ -1262,8 +1262,7 @@ Retry:
     ''' 释放 Java Wrapper 并返回完整文件路径。
     ''' </summary>
     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)
         SyncLock ExtractJavaWrapperLock '避免 OptiFine 和 Forge 安装时同时释放 Java Wrapper 导致冲突
             Try
@@ -1277,7 +1276,7 @@ Retry:
                         WriteFile(WrapperPath, GetResources("JavaWrapper"))
                     Catch ex2 As Exception
                         Log(ex2, "Java Wrapper 文件重新释放失败,将尝试更换文件名重新生成", LogLevel.Developer)
-                        WrapperPath = BaseDir & "\JavaWrapper2.jar"
+                        WrapperPath = PathPure & "JavaWrapper2.jar"
                         Try
                             WriteFile(WrapperPath, GetResources("JavaWrapper"))
                         Catch ex3 As Exception
@@ -1293,22 +1292,6 @@ Retry:
     End Function
     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 三部分的参数数据
     Private Sub McLaunchArgumentMain(Loader As LoaderTask(Of String, List(Of McLibToken)))
         McLaunchLog("开始获取 Minecraft 启动参数")
@@ -1408,7 +1391,7 @@ Retry:
                 Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent))
             Try
                 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.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
             Catch ex As Exception
@@ -1418,7 +1401,7 @@ Retry:
 
         '添加 Java Wrapper 作为主 Jar
         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() & """")
 
         '添加 MainClass
@@ -1475,7 +1458,7 @@ NextVersion:
                 Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent))
             Try
                 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.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
             Catch ex As Exception
@@ -1485,7 +1468,7 @@ NextVersion:
 
         '添加 Java Wrapper 作为主 Jar
         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() & """")
 
         '将 "-XXX" 与后面 "XXX" 合并到一起

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

@@ -1256,9 +1256,8 @@ OnLoaded:
         Dim VersionList As New List(Of McVersion)
 
 #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)
                 Continue For
             End If
@@ -1528,7 +1527,7 @@ OnLoaded:
     ''' 要求玩家选择一个皮肤文件,并进行相关校验。
     ''' </summary>
     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}
@@ -1935,7 +1934,7 @@ OnLoaded:
     ''' <summary>
     ''' 获取版本缺失的支持库文件所对应的 NetTaskFile。
     ''' </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
         Dim Result As New List(Of NetFile)
 
@@ -1948,7 +1947,6 @@ OnLoaded:
         Catch ex As Exception
             Log(ex, "版本缺失主 jar 文件所必须的信息", LogLevel.Developer)
         End Try
-        If CoreJarOnly Then Return Result
 
         'Library 文件
         Result.AddRange(McLibFixFromLibToken(McLibListGet(Version, False), JumpLoaderFolder:=Version.PathIndie & ".jumploader\"))
@@ -1957,15 +1955,15 @@ OnLoaded:
         If Setup.Get("VersionServerLogin", Version:=Version) = 3 Then
             Dim TargetFile = PathAppdata & "nide8auth.jar"
             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))
-                Catch ex As Exception
-                    Log(ex, "获取统一通行证下载信息失败")
-                End Try
+            Catch ex As Exception
+                Log(ex, "获取统一通行证下载信息失败")
+            End Try
             '校验文件
             If DownloadInfo IsNot Nothing Then
                 Dim Checker As New FileChecker(Hash:=DownloadInfo("jarHash").ToString)
@@ -1978,9 +1976,8 @@ OnLoaded:
         End If
 
         '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
             '获取下载信息
             Try
@@ -2001,9 +1998,9 @@ OnLoaded:
                             Replace("bmclapi2.bangbang93.com/mirrors/authlib-injector", "authlib-injector.yushi.moe")
                     Log("[Minecraft] Authlib-Injector 需要更新:" & DownloadAddress, LogLevel.Developer)
                     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

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

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

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

@@ -342,8 +342,8 @@ Retry:
             End If
             '删除原始整合包文件
             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)
                 End If
             Next
@@ -491,8 +491,8 @@ Retry:
             End If
             '删除原始整合包文件
             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)
                 End If
             Next

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

@@ -1,6 +1,7 @@
 <Grid x:Class="MyCompItem"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:local="clr-namespace:PCL"
              x:Name="PanBack" Height="64"
              RenderTransformOrigin="0.5,0.5" Background="{StaticResource ColorBrushSemiTransparent}" SnapsToDevicePixels="True">
     <Grid.ColumnDefinitions>
@@ -23,7 +24,7 @@
         <Border.Clip>
             <RectangleGeometry Rect="0,0,50,50" RadiusX="6" RadiusY="6" />
         </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>
     <!-- 标题 -->
     <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()
 
     'Logo
-    Private _Logo As String = ""
     Public Property Logo As String
         Get
-            Return _Logo
+            Return PathLogo.Source
         End Get
         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 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

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

@@ -24,7 +24,7 @@
         <Border.Clip>
             <RectangleGeometry Rect="0,0,34,34" RadiusX="6" RadiusY="6" />
         </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>
     <!-- 标题 -->
     <Grid Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="1" VerticalAlignment="Bottom" Margin="0,1,0,0"
@@ -52,6 +52,6 @@
         </Border>-->
     </StackPanel>
     <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" />
 </Grid>

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

@@ -6,88 +6,14 @@ Public Class MyLocalModItem
     Public Uuid As Integer = GetUuid()
 
     'Logo
-    Private _Logo As String = ""
     Public Property Logo As String
         Get
-            Return _Logo
+            Return PathLogo.Source
         End Get
         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 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
@@ -143,9 +69,9 @@ RetryStart:
             For Each TagText In value
                 Dim NewTag = GetObjectFromXML(
                 "<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"">
-                   <TextBlock Text=""" & TagText & """ Foreground=""#868686"" FontSize=""11"" />
+                   <TextBlock Text=""" & TagText & """ Foreground=""#88000000"" FontSize=""11"" />
                 </Border>")
                 PanTags.Children.Add(NewTag)
             Next
@@ -377,7 +303,7 @@ RetryStart:
 
 #End Region
 
-    Public Sub Refresh()
+    Public Sub Refresh() Handles Me.Loaded
         RunInUi(
         Sub()
             '更新
@@ -404,30 +330,45 @@ RetryStart:
             Else
                 BtnUpdate.Visibility = Visibility.Collapsed
             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
                 Case McMod.McModState.Fine
-                    NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path)
+                    DescFileName = GetFileNameWithoutExtentionFromPath(Entry.Path)
                 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
-                    NewDescription = GetFileNameFromPath(Entry.Path)
+                    DescFileName = GetFileNameFromPath(Entry.Path)
             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
                 NewDescription += ": " & Entry.Comp.Description.Replace(vbCr, "").Replace(vbLf, "")
             ElseIf Entry.Description IsNot Nothing Then
@@ -436,6 +377,11 @@ RetryStart:
                 NewDescription += ": " & "存在错误,无法获取信息"
             End If
             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 = If(Entry.Comp Is Nothing, PathImage & "Icons/NoIcon.png", Entry.Comp.GetControlLogo())
             '图标右下角的 Logo

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

@@ -92,7 +92,18 @@
                         MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的下载协议。", "事件执行失败")
                         Exit Sub
                     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
                     MyMsgBox("未知的事件类型:" & Type & vbCrLf & "请检查事件类型填写是否正确,或者 PCL 是否为最新版本。", "事件执行失败")

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

@@ -101,8 +101,8 @@ Friend Module ModSecret
     ''' 设置 Headers 的 UA、Referer。
     ''' </summary>
     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
             Client.Headers("User-Agent") = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
         Else
@@ -115,8 +115,8 @@ Friend Module ModSecret
     ''' 设置 Headers 的 UA、Referer。
     ''' </summary>
     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
             Request.UserAgent = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
         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("")>

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

@@ -1,4 +1,5 @@
 Imports System.IO.Compression
+Imports System.Net.Http
 
 Public Module ModDownloadLib
 
@@ -59,47 +60,6 @@ Public Module ModDownloadLib
             Return Nothing
         End Try
     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>
     ''' 获取下载某个 Minecraft 版本的加载器列表。
@@ -207,19 +167,14 @@ Public Module ModDownloadLib
         ToolTipService.SetVerticalOffset(BtnInfo, 30)
         ToolTipService.SetHorizontalOffset(BtnInfo, 2)
         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.SetVerticalOffset(BtnServer, 30)
         ToolTipService.SetHorizontalOffset(BtnServer, 2)
-        AddHandler BtnServer.Click, AddressOf McDownloadMenuServer
+        AddHandler BtnServer.Click, AddressOf McDownloadMenuSaveServer
         sender.Buttons = {BtnServer, BtnInfo}
     End Sub
     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 = "另存为"}
         ToolTipService.SetPlacement(BtnSave, Primitives.PlacementMode.Center)
         ToolTipService.SetVerticalOffset(BtnSave, 30)
@@ -230,7 +185,12 @@ Public Module ModDownloadLib
         ToolTipService.SetVerticalOffset(BtnInfo, 30)
         ToolTipService.SetHorizontalOffset(BtnInfo, 2)
         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
     Private Sub McDownloadMenuLog(sender As Object, e As RoutedEventArgs)
         Dim Version As JToken
@@ -243,60 +203,74 @@ Public Module ModDownloadLib
         End If
         McUpdateLogShow(Version)
     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
-            Version = sender.Parent.Parent.Tag
+            Version = sender.Parent.Parent
         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
     Public Sub McDownloadMenuSave(sender As Object, e As RoutedEventArgs)
         Dim Version As MyListItem
@@ -307,7 +281,41 @@ Public Module ModDownloadLib
         Else
             Version = sender.Parent.Parent
         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
     ''' <summary>
     ''' 显示某 Minecraft 版本的更新日志。
@@ -424,7 +432,7 @@ Public Module ModDownloadLib
         '添加 Java Wrapper 作为主 Jar
         Dim Arguments As String
         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
             Arguments = $"-Duser.home=""{BaseMcFolderHome}"" -cp ""{Target}"" optifine.Installer"
         End If
@@ -1091,7 +1099,7 @@ Retry:
         '添加 Java Wrapper 作为主 Jar
         Dim Arguments As String
         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
             Arguments = $"-cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" com.bangbang93.ForgeInstaller ""{McFolder}"
         End If
@@ -1446,8 +1454,6 @@ Retry:
                         '没有新增文件夹
                         Log("[Download] 未找到新增的版本文件夹")
                     End If
-                    '新建 mods 文件夹
-                    Directory.CreateDirectory(New McVersion(VersionFolder).GetPathIndie(True) & "mods\")
                 Catch ex As Exception
                     Throw New Exception($"安装新 {LoaderName} 版本失败", ex)
                 Finally
@@ -1463,57 +1469,55 @@ Retry:
         Else
             Log("[Download] 检测为非新版 Forge:" & LoaderVersion)
             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 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://meta.fabricmc.net/v2/versions/loader/" & MinecraftName & "/" & FabricVersion & "/profile/json"
             }, VersionFolder & Id & ".json", New FileChecker(IsJson:=True))}
-            '新建 mods 文件夹
-            Directory.CreateDirectory(New McVersion(VersionFolder).GetPathIndie(True) & "mods\")
         End Sub) With {.ProgressWeight = 0.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
             (Request.FabricVersion IsNot Nothing OrElse '2. 选择了 Fabric...
             (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
             Log("[Download] OptiFine 将作为 Mod 进行下载")
-            OptiFineFolder = New McVersion(OutputFolder).GetPathIndie(True) & "mods\"
+            OptiFineFolder = ModsFolder
         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})
         'Fabric API
         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
         'OptiFabric
         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
         '原版
         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)
                 Task.Progress = 0.3
                 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})
         '补全文件
         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 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 LoaderTask(Of Integer, Integer)("准备安装整合包",
             Sub()

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

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

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

@@ -13,12 +13,18 @@
     Private Shared Function LoaderInput() As CompProjectRequest
         Dim Request As New CompProjectRequest(CompType.Mod, Storage, (Page + 1) * PageSize)
         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
                 .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
-                .ModLoader = Val(FrmDownloadMod.ComboSearchLoader.SelectedItem.Tag)
+                .ModLoader = ModLoader
                 .Source = CType(Val(FrmDownloadMod.ComboSearchSource.SelectedItem.Tag), CompSourceType)
             End With
         End If

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

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

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

@@ -177,8 +177,8 @@
         Sub()
             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}
                     SkinLoader.WaitForExit(IsForceRestart:=True)

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

@@ -296,7 +296,7 @@
             Case 0 '正版或离线
 UnknownType:
                 If RadioLoginType5.Checked Then
-                    If Setup.Get("CacheMsAccess") = "" Then
+                    If Setup.Get("CacheMsV2Access") = "" Then
                         Type = PageType.Ms
                     Else
                         Type = PageType.MsSkin
@@ -311,7 +311,7 @@ UnknownType:
                 RadioLoginType5.Visibility = Visibility.Visible
                 RadioLoginType0.Visibility = Visibility.Visible
             Case 1 '仅正版
-                If Setup.Get("CacheMsAccess") = "" Then
+                If Setup.Get("CacheMsV2Access") = "" Then
                     Type = PageType.Ms
                 Else
                     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)
     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
     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] 主页预设:更新日志")
                         Url = "https://updatehomepage.pages.dev/UpdateHomepage.xaml"
                         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
         RunInUi(Sub() LoadContent(Content))

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

@@ -37,7 +37,7 @@
     ''' 获取当前页面的登录信息。
     ''' </summary>
     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
         RunInUiWait(
         Sub()

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

@@ -12,7 +12,7 @@
     ''' 刷新页面显示的所有信息。
     ''' </summary>
     Public Sub Reload(KeepInput As Boolean)
-        TextName.Text = Setup.Get("CacheMsName")
+        TextName.Text = Setup.Get("CacheMsV2Name")
         '皮肤在 Loaded 加载
     End Sub
     ''' <summary>
@@ -20,9 +20,9 @@
     ''' </summary>
     Public Shared Function GetLoginData() As McLoginMs
         If McLoginMsLoader.State = LoadState.Finished Then
-            Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsOAuthRefresh"), .UserName = Setup.Get("CacheMsName"), .AccessToken = Setup.Get("CacheMsAccess"), .Uuid = Setup.Get("CacheMsUuid"), .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
-            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 Function
 
@@ -50,11 +50,11 @@
 
     '退出登录
     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()
         FrmLaunchLeft.RefreshPage(False, True)
     End Sub
@@ -90,8 +90,8 @@
             Try
 Retry:
                 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)}
                 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)
-        '获取数据
-        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
     ''' <summary>
     ''' 从用户列表中移除一位用户。提示信息视作该用户主动离开。

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

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

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

@@ -83,7 +83,7 @@
             PageChangeRun(PageGet(ID))
             PageID = ID
         Catch ex As Exception
-            Log(ex, "切换设置分页面失败(ID " & ID & ")", LogLevel.Feedback)
+            Log(ex, "切换分页面失败(ID " & ID & ")", LogLevel.Feedback)
         Finally
             AniControlEnabled -= 1
         End Try
@@ -127,8 +127,8 @@
     '打开网页
     Public Shared Sub TryFeedback() Handles ItemFeedback.Click
         If Not CanFeedback(True) Then Exit Sub
-        Select Case MyMsgBox("是否要打开反馈列表网页?" & vbCrLf & "如果无法打开该网页,请尝试使用加速器或 VPN。",
-                    "反馈提示", "提交新反馈", "查看反馈列表", "取消")
+        Select Case MyMsgBox("在提交新反馈前,建议先搜索反馈列表,以避免重复提交。" & vbCrLf & "如果无法打开该网页,请尝试使用加速器或 VPN。",
+                    "反馈", "提交新反馈", "查看反馈列表", "取消")
             Case 1
                 Feedback(True, False)
             Case 2
@@ -137,7 +137,7 @@
     End Sub
     Public Shared Sub TryVote() Handles ItemVote.Click
         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")
     End Sub
 

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

@@ -9,6 +9,8 @@
     ''' </summary>
     Private ReadOnly SetupDict As New Dictionary(Of String, SetupEntry) From {
         {"Identify", New SetupEntry("", Source:=SetupSource.Registry)},
+        {"WindowHeight", New SetupEntry(550)},
+        {"WindowWidth", New SetupEntry(900)},
         {"HintDownloadThread", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"HintNotice", 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)},
         {"CacheMsUuid", 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)},
         {"CacheNideClient", 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)},
         {"ToolDownloadVersion", 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)},
         {"ToolDownloadCert", New SetupEntry(False, 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)},
         {"ToolUpdateRelease", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolUpdateSnapshot", New SetupEntry(False, Source:=SetupSource.Registry)},
@@ -592,7 +600,7 @@
                     FrmSetupUI.PanLogoChange.Visibility = Visibility.Visible
                 End If
                 Try
-                    FrmMain.ImageTitleLogo.Source = New MyBitmap(Path & "PCL\Logo.png")
+                    FrmMain.ImageTitleLogo.Source = Path & "PCL\Logo.png"
                 Catch ex As Exception
                     FrmMain.ImageTitleLogo.Source = Nothing
                     Log(ex, "显示标题栏图片失败", LogLevel.Msgbox)

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

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

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

@@ -7,66 +7,80 @@
     PanScroll="{Binding ElementName=PanBack}">
     <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
         <StackPanel x:Name="PanMain" Margin="25,10">
-
             <local:MyCard Margin="0,15" Title="下载">
-                <StackPanel Margin="25,37,25,12">
+                <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="27" />
                             <RowDefinition Height="27" />
                             <RowDefinition Height="Auto" />
                             <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
-                            <RowDefinition Height="Auto" />
                         </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:MyComboBoxItem Content="尽量使用镜像源(加载快,但版本可能不是最新的)" IsSelected="True" />
                             <local:MyComboBoxItem Content="仅在官方源加载缓慢时改用镜像源" />
                             <local:MyComboBoxItem Content="尽量使用官方源(保证版本最新,但加载缓慢)" />
                         </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="尽量使用官方源" IsSelected="True" />
                         </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" />
                             <local:MyComboBoxItem Content="译名位于文件名末尾:relics-0.1.14-遗物" />
                             <local:MyComboBoxItem Content="不添加中文译名:relics-0.1.14" />
                         </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" 
                                           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>
                 </StackPanel>
             </local:MyCard>

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

@@ -1,33 +1,5 @@
 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 Sub PageSetupSystem_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
@@ -58,11 +30,13 @@
         SliderDownloadThread.Value = Setup.Get("ToolDownloadThread")
         SliderDownloadSpeed.Value = Setup.Get("ToolDownloadSpeed")
         ComboDownloadVersion.SelectedIndex = Setup.Get("ToolDownloadVersion")
+        CheckDownloadCert.Checked = Setup.Get("ToolDownloadCert")
+
+        'Mod 与整合包
         ComboDownloadTranslate.SelectedIndex = Setup.Get("ToolDownloadTranslate")
         ComboDownloadMod.SelectedIndex = Setup.Get("ToolDownloadMod")
-        CheckDownloadKeepModpack.Checked = Setup.Get("ToolDownloadKeepModpack")
+        ComboModLocalNameStyle.SelectedIndex = Setup.Get("ToolModLocalNameStyle")
         CheckDownloadIgnoreQuilt.Checked = Setup.Get("ToolDownloadIgnoreQuilt")
-        CheckDownloadCert.Checked = Setup.Get("ToolDownloadCert")
 
         'Minecraft 更新提示
         CheckUpdateRelease.Checked = Setup.Get("ToolUpdateRelease")
@@ -91,10 +65,10 @@
             Setup.Reset("ToolDownloadSpeed")
             Setup.Reset("ToolDownloadVersion")
             Setup.Reset("ToolDownloadTranslate")
-            Setup.Reset("ToolDownloadKeepModpack")
             Setup.Reset("ToolDownloadIgnoreQuilt")
             Setup.Reset("ToolDownloadCert")
             Setup.Reset("ToolDownloadMod")
+            Setup.Reset("ToolModLocalNameStyle")
             Setup.Reset("ToolUpdateRelease")
             Setup.Reset("ToolUpdateSnapshot")
             Setup.Reset("ToolHelpChinese")
@@ -116,13 +90,13 @@
     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)
     End Sub
     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)
     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)
     End Sub
     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="PCL 主页市场(作者:凌云)" />
                             <local:MyComboBoxItem Content="PCL 更新日志(作者:Joker2184)" />
+                            <local:MyComboBoxItem Content="PCL 新功能说明书(作者:WForst-Breeze)" />
+                            <local:MyComboBoxItem Content="OpenMCIM 仪表盘(作者:SALTWOOD)" />
                         </local:MyComboBox>
                     </Grid>
                 </StackPanel>

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

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

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

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

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

@@ -30,7 +30,7 @@
     ''' 刷新 Mod 列表。
     ''' </summary>
     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 列表")
             Filter = FilterType.All
             PanBack.ScrollToHome()
@@ -43,9 +43,12 @@
     End Sub
     Private Sub Load_Click(sender As Object, e As MouseButtonEventArgs) Handles Load.Click
         If McModLoader.State = LoadState.Failed Then
-            LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.ForceRun)
+            LoaderRun(LoaderFolderRunType.ForceRun)
         End If
     End Sub
+    Public Function LoaderRun(Type As LoaderFolderRunType) As Boolean
+        Return LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", Type)
+    End Function
 
 #End Region
 
@@ -329,7 +332,7 @@
     Private Sub ChangeAllSelected(Value As Boolean)
         AniControlEnabled += 1
         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
             If Value Then SelectedMods.Add(Item.Entry.RawFileName)
         Next
@@ -467,10 +470,12 @@
             '更改 Loader 中的列表
             Dim NewModEntity As New McMod(NewPath)
             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)
                 SearchResult.Remove(ModEntity)
                 SearchResult.Insert(IndexOfResult, NewModEntity)
@@ -489,6 +494,7 @@
             Hint("由于文件被占用,Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
             ReloadModList(True)
         End If
+        LoaderRun(LoaderFolderRunType.UpdateOnly)
     End Sub
 
     '更新
@@ -696,6 +702,7 @@
             Log(ex, "删除 Mod 出现未知错误", LogLevel.Feedback)
             ReloadModList(True)
         End Try
+        LoaderRun(LoaderFolderRunType.UpdateOnly)
     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\MyComboBoxItem.vb" />
     <Compile Include="Controls\MyCard.vb" />
+    <Compile Include="Controls\MyImage.vb" />
     <Compile Include="Controls\MyPageRight.vb" />
     <Compile Include="Controls\MyPageLeft.vb" />
     <Compile Include="Controls\MyIconTextButton.xaml.vb">
       <DependentUpon>MyIconTextButton.xaml</DependentUpon>
     </Compile>
-    <Compile Include="Controls\MyImage.vb" />
+
+
+
     <Compile Include="Modules\Minecraft\ModComp.vb" />
     <Compile Include="Modules\Minecraft\ModJava.vb" />
     <Compile Include="Modules\Minecraft\ModMod.vb" />
@@ -476,6 +479,7 @@
       <DependentUpon>FormMain.xaml</DependentUpon>
       <SubType>Code</SubType>
     </Compile>
+
     <Page Include="Modules\Minecraft\MyLocalModItem.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
@@ -910,6 +914,7 @@
     <Resource Include="Images\Heads\MCBBS.png" />
   </ItemGroup>
   <ItemGroup>
+
   </ItemGroup>
   <ItemGroup>
     <Resource Include="Images\Blocks\NeoForge.png" />

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

@@ -4,311 +4,309 @@
      你也可以使用 Ctrl + F 快速查找。例如,若需要改变文本颜色,则搜索 “颜色” 即可。 -->
 
 <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"
                     Text="每个 local:MyCard 代表一张卡片,你可以添加、删除格式类似的 MyCard 来添加多个卡片。每个 TextBlock 代表一段文本,你可以在 Text 属性中书写任何你想写的内容,也可以自行添加更多的 TextBlock。" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4" FontSize="11"
                     Text="你可以通过添加、删除属性修改样式,例如上一行的 FontSize 就会将字号改为 11 号。" />
         <TextBlock TextWrapping="Wrap" Margin="20,5,20,0" Foreground="#8C7721"
                     Text="它还有许多可以调整的属性:上一行的 Margin 调整了边距,Foreground 则会让文字变色。" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <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 转义字符。" />
         <TextBlock TextWrapping="Wrap" Margin="0,6,0,0"
                     Text="卡片(local:MyCard)的 Title 属性决定了它的标题。&#xA;为卡片添加 CanSwap 属性让它可以被折叠,True 代表是,False 代表否。在此基础上,再使用 IsSwaped 属性调整它是否默认被折叠。" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <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"
                     Text="任意项目都可以添加 Width 与 Height 属性来控制宽高。HorizontalAlignment 属性可以控制对齐:Center 代表居中,Right 代表居右,例如这段文本就被居右了。" />
         <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 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"
                     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="这是一个按钮!一个按钮!" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     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="……就能看到这句话!" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="local:MyTextButton 是没有边框的 “纯文本按钮”,其作用与 local:MyButton 一致。" />
-		<local:MyTextButton Margin="0,8,0,10" HorizontalAlignment="Center"
+        <local:MyTextButton Margin="0,8,0,10" HorizontalAlignment="Center"
                     Text="一个精简版的文本按钮" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="按钮可以做到很多事,具体将在 “事件” 部分介绍。" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <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"
                     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/" />
         <TextBlock TextWrapping="Wrap" Margin="0,10,0,10"
                     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你也可以直接在其中换行。" />
         <TextBlock TextWrapping="Wrap" Margin="0,10,0,0"
                     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/" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <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"
                     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" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     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" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     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="启动当前版本" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <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"
                     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" />
         <TextBlock TextWrapping="Wrap" Margin="0,4,0,0"
                     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}),可以用于执行特定程序或加载图片。" />
-		<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" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     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;点击按钮会打开文件管理器,并且借助参数要求它选中这个文件。" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <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 共有以下种类:" />
-		<TextBlock Margin="0,0,0,4"
+        <TextBlock Margin="0,0,0,4"
                     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"/>
-		<TextBlock Margin="0,0,0,4"
+        <TextBlock Margin="0,0,0,4"
                     Text="· 今日人品、清理垃圾、内存优化、安装整合包:触发对应功能" />
-		<TextBlock Margin="0,0,0,4"
+        <TextBlock Margin="0,0,0,4"
                     Text="· 复制文本:将 EventData 中的内容复制到剪贴板" />
-		<TextBlock Margin="0,0,0,4"
+        <TextBlock Margin="0,0,0,4"
                     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" />
-		<TextBlock Margin="0,4,0,4"
+        <TextBlock Margin="0,4,0,4"
                     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"/>
-		<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"/>
-		<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"/>
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <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 在一行里塞下多个按钮。你可以仅在这个教学卡片的基础上稍作调整,来实现自己的按钮布局。" />
-		<!-- 这是第一行 -->
-		<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/" />
-			<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" />
-			<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" />
-		</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 命令会在每个玩家头顶召唤一只苦力怕!" />
-			<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="内存优化" />
-			<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" />
-		</StackPanel>
-	</StackPanel>
+        </StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <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"
                     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"
                     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"
-		            EventType="打开帮助" EventData="帮助/自定义帮助.json" Type="Clickable" />
+                    EventType="打开帮助" EventData="帮助/自定义帮助.json" Type="Clickable" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="EventType、EventData 与按钮几乎一致,唯一不同的是,列表项不支持联网加载帮助文件。" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <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"
                     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"
                     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"
             Title="清理垃圾" Info="使用内置的命令方块图片作为 Logo" EventType="清理垃圾" Type="Clickable" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <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"
                     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=" 浓度 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=" 浓度 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=" 浓度 6 " Margin="5,0" Background="{DynamicResource ColorBrush6}" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 7 " Margin="5,0" Background="{DynamicResource ColorBrush7}" />
             <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。" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <local:MyCard Title="替换标记" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-	<StackPanel Margin="25,40,23,15">
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+    <StackPanel Margin="25,40,23,15">
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="在花括号 {} 中写入特定内容,PCL 会在加载时对其进行替换。&#xa;例如,path 会被替换为 PCL 可执行文件所在文件夹({path}),用于在 EventData 中指定特定文件,或是加载图片。" />
-		<local:MyHint Margin="0,6,0,10" Text="以下特性于 PCL 2.6.5 (299) 加入,不适用于更早版本的 PCL。" IsWarn="False" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+        <local:MyHint Margin="0,6,0,10" Text="以下特性于 PCL 2.6.5 (299) 加入,不适用于更早版本的 PCL。" IsWarn="False" />
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="你也可以使用替换标记来获取随机内容:cave 会被替换为一条随机的回声洞内容,hint 则是 “你知道吗” 的内容。" />
-		<TextBlock TextWrapping="Wrap" Margin="10,6,0,5" Foreground="{DynamicResource ColorBrush3}"
+        <TextBlock TextWrapping="Wrap" Margin="10,6,0,5" Foreground="{DynamicResource ColorBrush3}"
                     Text="你知道吗:{hint}" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <local:MyCard Title="进阶:Grid 布局" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-	<StackPanel Margin="25,40,23,15">
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+    <StackPanel Margin="25,40,23,15">
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="如果要实现更复杂的布局,则必须使用 Grid。Grid 可以让按钮们自动适应窗口宽度:随意拉伸 PCL 窗口,按钮大小会自动改变。" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
                     Text="你可以上网查找 “WPF Grid” 的相关教程,这里仅给出一个例子,不作详细介绍。你可以仅在这个教学卡片的基础上稍作调整,来实现自己的按钮布局。" />
-		<Grid>
-			<Grid.ColumnDefinitions>
-				<ColumnDefinition Width="1*" />
-				<ColumnDefinition Width="1.6*" MinWidth="200" />
-				<!-- 第二个按钮的宽度是第一个按钮的 1.6 倍,且至少为 200 -->
-				<ColumnDefinition Width="150" />
-				<!-- 如果不打 *,则会占用固定的宽度,也可以写 Auto 来自适应 -->
-			</Grid.ColumnDefinitions>
-			<!-- 为按钮添加 Grid.Column 属性指定它所在的列,不要添加 Width 与 HorizontalAlignment 属性 -->
-			<local:MyButton Grid.Column="0" Margin="0,0,10,0" Height="35" ColorType="Highlight"
+        <Grid>
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="1*" />
+                <ColumnDefinition Width="1.6*" MinWidth="200" /> <!-- 第二个按钮的宽度是第一个按钮的 1.6 倍,且至少为 200 -->
+                <ColumnDefinition Width="150" /> <!-- 如果不打 *,则会占用固定的宽度,也可以写 Auto 来自适应 -->
+            </Grid.ColumnDefinitions>
+            <!-- 为按钮添加 Grid.Column 属性指定它所在的列,不要添加 Width 与 HorizontalAlignment 属性 -->
+            <local:MyButton Grid.Column="0" Margin="0,0,10,0" Height="35" ColorType="Highlight"
                         Text="打开 B 站" EventType="打开网页" EventData="https://www.bilibili.com/" />
-			<local:MyButton Grid.Column="1" Margin="0,0,10,0" Height="35"
+            <local:MyButton Grid.Column="1" Margin="0,0,10,0" Height="35"
                         Text="进入 Hypixel" EventType="启动游戏" EventData="1.16.3|mc.hypixel.net" ToolTip="使用 Minecraft 1.16.3 进入 Hypixel" />
-			<local:MyButton Grid.Column="2" Margin="0,0,10,0" Height="35"
+            <local:MyButton Grid.Column="2" Margin="0,0,10,0" Height="35"
                         Text="打开记事本" EventType="打开文件" EventData="notepad" />
-		</Grid>
-	</StackPanel>
+        </Grid>
+    </StackPanel>
 </local:MyCard>
 
 <local:MyCard Title="进阶:图标按钮" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-	<StackPanel Margin="25,40,23,15">
-		<local:MyHint Margin="0,0,0,10" Text="以下特性于 PCL 2.6.5 (299) 加入,不适用于更早版本的 PCL。" IsWarn="False" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+    <StackPanel Margin="25,40,23,15">
+        <local:MyHint Margin="0,0,0,10" Text="以下特性于 PCL 2.6.5 (299) 加入,不适用于更早版本的 PCL。" IsWarn="False" />
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="MyIconTextButton 是一种带有自定义矢量图标的按钮变体,它同样支持 EventType 与 EventData 属性。&#xa;通过设置 Logo 属性来控制它的图标,设置 LogoScale 属性还可以调整图标的缩放比例。" />
-		<local:MyIconTextButton Margin="0,4,0,8" Height="35" HorizontalAlignment="Center"
+        <local:MyIconTextButton Margin="0,4,0,8" Height="35" HorizontalAlignment="Center"
                     Text="查看 SVG Path 简介" EventType="打开网页" EventData="https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Paths"
                     LogoScale="1.02" Logo="M1091 0H78C35 0 0 35 0 78v863c0 43 35 78 78 78H1091c43 0 78-35 78-78V78C1170 35 1134 0 1091 0z m-8 87v78H87v-78h994zM87 933V254h994v679H87v0zM724 340l-206 547a43 43 0 0 1-82-31l206-547a43 43 0 0 1 82 30zM369 545l-85 85 85 85a43 43 0 0 1-62 62l-116-116a43 43 0 0 1 0-62l116-116a43 43 0 0 1 62 61z m610-37a43 43 0 0 1 0 62l-116 116a43 43 0 0 1-62 0 43 43 0 0 1 0-62l85-85-85-85a43 43 0 0 1 62-62l116 116z" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="Logo 属性的内容是 SVG Path 的值,你需要使用 SVG 编辑器或者从图标资源网站(如 IconFont)查找它。不要自己写,写不懂的。&#xa;此外,它还有两种 ColorType:Black(默认)和 Highlight。" />
-		<local:MyIconTextButton Margin="0,4,0,8" Height="35" HorizontalAlignment="Center"
+        <local:MyIconTextButton Margin="0,4,0,8" Height="35" HorizontalAlignment="Center"
                     ColorType="Highlight" Text="打开 IconFont" EventType="打开网页" EventData="https://www.iconfont.cn/"
                     LogoScale="1" Logo="M510 959c-51-31-40-86-74-120-61-63-168-66-231-138C-5 457 196 3 593 70c231 39 407 363 250 592-67 98-198 106-277 213-23 23-11 81-55 83zM176 432c-9 176 240 167 240 9 0-80-72-145-166-111-66 23-71 49-74 101z m509 129c212 59 217-307 0-231-102 36-90 206 0 231z m-213 111c33 7 44-91 9-92-19 19-64 79-9 92z m83 0H575c23-37-8-87-46-92 3 37-11 92 27 92z" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="如果只想要图标,不要文字,还可以使用 MyIconButton。它不能设置 Text 属性,只有一个图标可以按。&#xa;它的 Theme 属性可以设置为 Color(默认)、White、Black、Red。" />
-		<local:MyIconButton Margin="0,4,0,0" Width="25" Height="25" HorizontalAlignment="Center"
+        <local:MyIconButton Margin="0,4,0,0" Width="25" Height="25" HorizontalAlignment="Center"
                     Theme="Black" EventType="弹出窗口" EventData="事件|MyIconButton 当然也支持 EventType 和 EventData——要不然介绍它干啥?"
                     LogoScale="1" Logo="M149 873c47 47 101 83 162 109 63 26 130 40 199 40 69 0 136-13 199-40 61-25 115-62 162-109s83-101 109-162c26-63 40-130 40-199 0-69-13-136-40-199-25-61-62-115-109-162-46-46-101-83-162-109C648 13 580 0 511 0s-136 13-199 40c-61 25-115 62-162 109s-83 101-109 162C13 375 0 442 0 511s13 136 40 199c25 61 62 115 109 162zM97 511c0-228 185-414 414-414 228 0 414 185 414 414S740 926 511 926c-228 0-414-185-414-414z M539 244c-8-4-17-7-27-7-9 0-19 2-27 7-16 9-27 27-27 47 0 30 24 54 54 54 30 0 54-24 54-54 0-19-10-37-27-47zM566 732v-284c0-30-24-54-54-54-30 0-54 24-54 54v284c0 30 24 54 54 54 30 0 54-24 54-54z" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="如果只想显示一个图标,不想让它是能按的按钮,可以使用 WPF 自带的 Path 等绘图控件,感兴趣的话可以自行搜索。想偷懒也可以把 MyIconButton 的 IsHitTestVisible 属性改为 False……" />
-		<Path Margin="0,4,0,0" Width="20" Height="20" HorizontalAlignment="Center"
+        <Path Margin="0,4,0,0" Width="20" Height="20" HorizontalAlignment="Center"
                     Stretch="Uniform" Fill="{DynamicResource ColorBrush1}"
                     Data="M149 873c47 47 101 83 162 109 63 26 130 40 199 40 69 0 136-13 199-40 61-25 115-62 162-109s83-101 109-162c26-63 40-130 40-199 0-69-13-136-40-199-25-61-62-115-109-162-46-46-101-83-162-109C648 13 580 0 511 0s-136 13-199 40c-61 25-115 62-162 109s-83 101-109 162C13 375 0 442 0 511s13 136 40 199c25 61 62 115 109 162zM97 511c0-228 185-414 414-414 228 0 414 185 414 414S740 926 511 926c-228 0-414-185-414-414z M539 244c-8-4-17-7-27-7-9 0-19 2-27 7-16 9-27 27-27 47 0 30 24 54 54 54 30 0 54-24 54-54 0-19-10-37-27-47zM566 732v-284c0-30-24-54-54-54-30 0-54 24-54 54v284c0 30 24 54 54 54 30 0 54-24 54-54z" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <local:MyCard Title="进阶:联网自定义主页" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-	<StackPanel Margin="25,40,23,15">
-		<local:MyHint Margin="0,0,0,10" Text="以下特性于 PCL 2.6.5 (299) 加入,不适用于更早版本的 PCL。" IsWarn="False" />
-		<TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
+    <StackPanel Margin="25,40,23,15">
+        <local:MyHint Margin="0,0,0,10" Text="以下特性于 PCL 2.6.5 (299) 加入,不适用于更早版本的 PCL。" IsWarn="False" />
+        <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
                     Text="如果你在尝试制作联网更新的自定义主页,可以查看下方的 GitHub 讨论页。&#xa;你可以根据其中的介绍为主页添加版本号检查以节省流量,也可以通过检查 Referer 和 User Agent 来确定对方的 PCL 版本。" />
-		<local:MyButton Margin="0,0,0,8" Height="35" HorizontalAlignment="Center" Padding="20,0,20,0"
+        <local:MyButton Margin="0,0,0,8" Height="35" HorizontalAlignment="Center" Padding="20,0,20,0"
                     Text="打开 GitHub 讨论页" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2/discussions/2528" />
-	</StackPanel>
+    </StackPanel>
 </local:MyCard>
 
 <!-- 
@@ -317,9 +315,9 @@
  - Width、Height:设置宽度与高度
  - ToolTip:设置鼠标指向时的提示文本
  - 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(纯文本):
  - Text:设置显示的文本(如果要使用引号等特殊字符,自行百度 XAML 转义)
@@ -328,17 +326,20 @@ TextBlock(纯文本):
  - FontWeight:设置为 Bold 时,文字加粗
  - Foreground:调整前景颜色(十六进制字符串,例如 #FF010203 代表 ARGB 中的 255,1,2,3。前两位代表不透明度,FF 为全不透明,00 为全透明)
 
-Image(图片):
+local:MyImage(图片):
  - Source:要显示的图片的网址,或 PCL 内置图片位置
+ - FallbackSource:当 Source 首次下载失败时,会从该备用地址加载图片
+ - LoadingSource:正在下载网络图片时显示的本地图片(不支持联网下载);默认为 pack://application:,,,/images/Icons/NoIcon.png
+ - EnableCache:若图片是联网获取的,是否将图片保存到本地缓存,在 7 天内不再重新下载;True 为是(默认),False 为否
 
 local:MyCard(卡片):
  - Title:设置显示的标题文本
  - CanSwap:卡片是否可以折叠,True 为是,False 为否
  - IsSwaped:卡片是否默认折叠,要求 CanSwap 必须为 True
  - HorizontalAlignment:若使用,要求 CanSwap 必须为 False
- - UseAnimation:是否在展开等高度改变时触发动画,True 为是(默认),False 为否
- - SwapLogoRight:卡片折叠时的箭头是朝下还是朝右,True 为朝右,False 为朝下(默认)
- - HasMouseAnimation:是否在鼠标指向时改变卡片颜色,True 为是(默认),False 为否
+ - UseAnimation:是否在展开等高度改变时触发动画;True 为是(默认),False 为否
+ - SwapLogoRight:卡片折叠时的箭头是朝下还是朝右;True 为朝右,False 为朝下(默认)
+ - HasMouseAnimation:是否在鼠标指向时改变卡片颜色;True 为是(默认),False 为否
 
 local:MyHint(提示条):
  - Text:设置显示的文本(&#xA; 代表换行)

BIN
Plain Craft Launcher 2/Resources/Help.zip


Разница между файлами не показана из-за своего большого размера
+ 116 - 116
Plain Craft Launcher 2/Resources/ModData.txt


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