龙腾猫跃 2 лет назад
Родитель
Сommit
2c7b532188
50 измененных файлов с 1535 добавлено и 765 удалено
  1. 1 1
      Plain Craft Launcher 2/Application.xaml
  2. 3 3
      Plain Craft Launcher 2/Application.xaml.vb
  3. 1 1
      Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb
  4. 1 1
      Plain Craft Launcher 2/Controls/MyListItem.xaml
  5. 39 42
      Plain Craft Launcher 2/Controls/MyListItem.xaml.vb
  6. 30 62
      Plain Craft Launcher 2/Controls/MyLoading.xaml.vb
  7. 23 11
      Plain Craft Launcher 2/FormMain.xaml.vb
  8. BIN
      Plain Craft Launcher 2/Images/Icons/Disabled.png
  9. BIN
      Plain Craft Launcher 2/Images/Icons/NoIcon.png
  10. BIN
      Plain Craft Launcher 2/Images/Icons/Unavaliable.png
  11. 20 10
      Plain Craft Launcher 2/Modules/Base/ModBase.vb
  12. 11 3
      Plain Craft Launcher 2/Modules/Base/ModLoader.vb
  13. 310 245
      Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
  14. 2 2
      Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
  15. 3 3
      Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
  16. 3 0
      Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
  17. 5 4
      Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
  18. 4 6
      Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
  19. 266 22
      Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
  20. 3 3
      Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml
  21. 92 23
      Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb
  22. 43 0
      Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml
  23. 453 0
      Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb
  24. 7 5
      Plain Craft Launcher 2/Modules/ModMusic.vb
  25. 2 2
      Plain Craft Launcher 2/My Project/AssemblyInfo.vb
  26. 17 21
      Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
  27. 6 19
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb
  28. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml
  29. 5 5
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml
  30. 2 0
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb
  31. 2 0
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml
  32. 7 0
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb
  33. 2 0
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml
  34. 7 0
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml.vb
  35. 2 1
      Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
  36. 4 0
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb
  37. 5 5
      Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml
  38. 3 3
      Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb
  39. 1 2
      Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb
  40. 6 6
      Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml
  41. 5 5
      Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml
  42. 1 1
      Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb
  43. 1 1
      Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb
  44. 1 1
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml
  45. 5 5
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml
  46. 8 1
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb
  47. 13 15
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml
  48. 98 224
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
  49. 11 0
      Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
  50. BIN
      Plain Craft Launcher 2/Resources/Help.zip

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

@@ -345,7 +345,7 @@
 
             <!-- ComboBox -->
             <DataTemplate x:Key="ComboBoxItemTemplateWithDelete">
-                <local:MyListItem Title="{Binding}" FontSize="13" PaddingRight="40" MaxHeight="26" IsScaleAnimationEnabled="False" Margin="-3,0,0,0">
+                <local:MyListItem Title="{Binding}" FontSize="13" MaxHeight="26" IsScaleAnimationEnabled="False" Margin="-3,0,0,0">
                     <local:MyListItem.Buttons>
                         <x:Array Type="local:MyIconButton">
                             <local:MyIconButton Grid.Column="1" Height="20" Width="20" VerticalAlignment="Center" Tag="{Binding}" Click="MyIconButton_Click" Logo="F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z" />

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

@@ -116,9 +116,9 @@ Public Class Application
             ServicePointManager.Expect100Continue = True
             ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
             ServicePointManager.DefaultConnectionLimit = 1024
-            ServicePointManager.ServerCertificateValidationCallback = New Security.RemoteCertificateValidationCallback(Function() As Boolean
-                                                                                                                           Return True
-                                                                                                                       End Function)
+            'ServicePointManager.ServerCertificateValidationCallback = New Security.RemoteCertificateValidationCallback(Function() As Boolean
+            '                                                                                                               Return True
+            '                                                                                                           End Function)
             '计时
             Log("[Start] 第一阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
             ApplicationStartTick = GetTimeTick()

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

@@ -133,7 +133,7 @@
                     Dim AnimList As New List(Of AniData)
                     Select Case Theme
                         Case Themes.Color
-                            AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrush5", AnimationColorOut))
+                            AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrush4", AnimationColorOut))
                             PanBack.Background = New MyColor(0, 255, 255, 255)
                         Case Themes.White
                             AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrush8", AnimationColorOut))

+ 1 - 1
Plain Craft Launcher 2/Controls/MyListItem.xaml

@@ -11,7 +11,7 @@
         <ColumnDefinition x:Name="ColumnPaddingLeft" Width="0" />
         <ColumnDefinition x:Name="ColumnLogo" Width="4" />
         <ColumnDefinition Width="1*"/>
-        <ColumnDefinition x:Name="ColumnPaddingRight" Width="0" />
+        <ColumnDefinition x:Name="ColumnPaddingRight" Width="4" />
     </Grid.ColumnDefinitions>
     <Grid.RowDefinitions>
         <RowDefinition Height="1*" />

+ 39 - 42
Plain Craft Launcher 2/Controls/MyListItem.xaml.vb

@@ -104,42 +104,34 @@
             ColumnPaddingLeft.Width = New GridLength(value)
         End Set
     End Property
-    Public Property PaddingRight As Integer
-        Get
-            Return ColumnPaddingRight.Width.Value
-        End Get
-        Set(value As Integer)
-            ColumnPaddingRight.Width = New GridLength(value)
-        End Set
-    End Property
+    ''' <summary>
+    ''' 右边距的最小值。
+    ''' 在存在右侧按钮时,右边距会被自动设置为 5 + 按钮数 * 25。
+    ''' </summary>
+    Public Property MinPaddingRight As Integer = 4
 
     '按钮
-    Private _Buttons As IEnumerable
-    Public Property Buttons As IEnumerable
+    Private _Buttons As IEnumerable(Of MyIconButton)
+    Public Property Buttons As IEnumerable(Of MyIconButton)
         Get
             Return _Buttons
         End Get
-        Set(value As IEnumerable)
+        Set(value As IEnumerable(Of MyIconButton))
             _Buttons = value
             '没有特殊按钮,移除原 Stack
-            If Not IsNothing(ButtonStack) Then
+            If ButtonStack IsNot Nothing Then
                 Children.Remove(ButtonStack)
                 ButtonStack = Nothing
             End If
-            '获取按钮数量
-            Dim Count As Integer = 0
-            For Each Counter In value
-                Count += 1
-            Next
             '添加新 Stack
-            Select Case Count
+            Select Case value.Count
                 Case 0
                     '没有按钮,不添加新的
                 Case 1
                     '只有一个按钮
                     For Each Btn As MyIconButton In value
-                        If Btn.Height.Equals(Double.NaN) Then Btn.Height = 23
-                        If Btn.Width.Equals(Double.NaN) Then Btn.Width = 23
+                        If Btn.Height.Equals(Double.NaN) Then Btn.Height = 25
+                        If Btn.Width.Equals(Double.NaN) Then Btn.Width = 25
                         With Btn
                             .Opacity = 0
                             .Margin = New Thickness(0, 0, 5, 0)
@@ -149,22 +141,18 @@
                             .SnapsToDevicePixels = False
                             .UseLayoutRounding = False
                         End With
-                        SetColumn(Btn, 4)
-                        SetColumnSpan(Btn, 4)
-                        SetRowSpan(Btn, 4)
+                        SetColumnSpan(Btn, 10) : SetRowSpan(Btn, 10)
                         Children.Add(Btn)
                         ButtonStack = Btn
                     Next
                 Case Else
                     '有复数按钮,使用 StackPanel
                     ButtonStack = New StackPanel With {.Opacity = 0, .Margin = New Thickness(0, 0, 5, 0), .SnapsToDevicePixels = False, .Orientation = Orientation.Horizontal, .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Center, .UseLayoutRounding = False}
-                    SetColumn(ButtonStack, 4)
-                    SetColumnSpan(ButtonStack, 4)
-                    SetRowSpan(ButtonStack, 4)
+                    SetColumnSpan(ButtonStack, 10) : SetRowSpan(ButtonStack, 10)
                     '构造按钮
                     For Each Btn As MyIconButton In value
-                        If Btn.Height.Equals(Double.NaN) Then Btn.Height = 23
-                        If Btn.Width.Equals(Double.NaN) Then Btn.Width = 23
+                        If Btn.Height.Equals(Double.NaN) Then Btn.Height = 25
+                        If Btn.Width.Equals(Double.NaN) Then Btn.Width = 25
                         CType(ButtonStack, StackPanel).Children.Add(Btn)
                     Next
                     Children.Add(ButtonStack)
@@ -324,7 +312,8 @@
                 '添加竖条控件
                 If IsNothing(RectCheck) Then
                     RectCheck = New Border With {.Width = 5, .Height = If(Checked, Double.NaN, 0), .CornerRadius = New CornerRadius(2, 2, 2, 2),
-                        .VerticalAlignment = If(Checked, VerticalAlignment.Stretch, VerticalAlignment.Center), .HorizontalAlignment = HorizontalAlignment.Left, .UseLayoutRounding = False, .SnapsToDevicePixels = False,
+                        .VerticalAlignment = If(Checked, VerticalAlignment.Stretch, VerticalAlignment.Center),
+                        .HorizontalAlignment = HorizontalAlignment.Left, .UseLayoutRounding = False, .SnapsToDevicePixels = False,
                         .Margin = If(Checked, New Thickness(-1, 6, 0, 6), New Thickness(-1, 0, 0, 0))}
                     RectCheck.SetResourceReference(Border.BackgroundProperty, "ColorBrush3")
                     SetRowSpan(RectCheck, 4)
@@ -532,10 +521,6 @@
                 Log("[Control] 按下复选列表项(" & (Not Checked).ToString & "):" & Title)
                 SetChecked(Not Checked, True, True)
         End Select
-        'AniStart({
-        '       AaScaleTransform(Me, 1.004 - CType(RenderTransform, ScaleTransform).ScaleX, 150,, New AniEaseOutFluent),
-        '       AaScaleTransform(Me, -0.004, 100, 150, New AniEaseInFluent)
-        '   }, "MyListItem Scale " & Uuid)
     End Sub
 
     '鼠标点击判定
@@ -543,15 +528,12 @@
     Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonDown
         If IsMouseDirectlyOver AndAlso Not Type = CheckType.None Then
             IsMouseDown = True
-            If Not IsNothing(ButtonStack) Then ButtonStack.IsHitTestVisible = False
+            If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = False
         End If
-        'AniStart({
-        '       AaScaleTransform(Me, 0.985 - CType(RenderTransform, ScaleTransform).ScaleX, 50,, New AniEaseOutFluent)
-        '   }, "MyListItem Scale " & Uuid)
     End Sub
     Private Sub Button_MouseLeave(sender As Object, e As Object) Handles Me.MouseLeave, Me.PreviewMouseLeftButtonUp
         IsMouseDown = False
-        If Not IsNothing(ButtonStack) Then ButtonStack.IsHitTestVisible = True
+        If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = True
     End Sub
 
     '实现自定义事件
@@ -605,7 +587,11 @@
             '有动画
             Dim Ani As New List(Of AniData)
             If IsMouseOver AndAlso IsMouseOverAnimationEnabled Then
-                If ButtonStack IsNot Nothing Then Ani.Add(AaOpacity(ButtonStack, 1 - ButtonStack.Opacity, Time * 0.7, Time * 0.3))
+                If ButtonStack IsNot Nothing Then
+                    Ani.Add(AaOpacity(ButtonStack, 1 - ButtonStack.Opacity, Time * 0.7, Time * 0.3))
+                    Ani.Add(AaDouble(Sub(i) ColumnPaddingRight.Width = New GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + i)),
+                                     Math.Max(MinPaddingRight, 5 + Buttons.Count * 25) - ColumnPaddingRight.Width.Value, Time * 0.3, Time * 0.7))
+                End If
                 Ani.AddRange({
                              AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrushBg1"), Time),
                              AaOpacity(RectBack, 1 - RectBack.Opacity, Time,, New AniEaseOutFluent)
@@ -619,7 +605,11 @@
                     End If
                 End If
             Else
-                If ButtonStack IsNot Nothing Then Ani.Add(AaOpacity(ButtonStack, -ButtonStack.Opacity, Time * 0.5))
+                If ButtonStack IsNot Nothing Then
+                    Ani.Add(AaOpacity(ButtonStack, -ButtonStack.Opacity, Time * 0.4))
+                    Ani.Add(AaDouble(Sub(i) ColumnPaddingRight.Width = New GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + i)),
+                                     MinPaddingRight - ColumnPaddingRight.Width.Value, Time * 0.4))
+                End If
                 Ani.Add(AaOpacity(RectBack, -RectBack.Opacity, Time))
                 If IsScaleAnimationEnabled Then
                     Ani.AddRange({
@@ -634,14 +624,20 @@
         Else
             '无动画
             If IsMouseOver AndAlso IsMouseOverAnimationEnabled Then
-                If ButtonStack IsNot Nothing Then ButtonStack.Opacity = 1
+                If ButtonStack IsNot Nothing Then
+                    ButtonStack.Opacity = 1
+                    ColumnPaddingRight.Width = New GridLength(Math.Max(MinPaddingRight, 5 + Buttons.Count * 25))
+                End If
                 '由于鼠标已经移入,所以直接实例化 RectBack
                 RectBack.Background = ColorBg1
                 RectBack.Opacity = 1
                 RectBack.RenderTransform = New ScaleTransform(1, 1)
                 Me.RenderTransform = New ScaleTransform(1, 1)
             Else
-                If ButtonStack IsNot Nothing Then ButtonStack.Opacity = 0
+                If ButtonStack IsNot Nothing Then
+                    ButtonStack.Opacity = 0
+                    ColumnPaddingRight.Width = New GridLength(MinPaddingRight)
+                End If
                 Me.RenderTransform = New ScaleTransform(1, 1)
                 If _RectBack IsNot Nothing Then
                     If IsScaleAnimationEnabled Then RectBack.RenderTransform = New ScaleTransform(0.75, 0.75)
@@ -659,6 +655,7 @@
         Else
             SetResourceReference(ForegroundProperty, "ColorBrush1")
         End If
+        ColumnPaddingRight.Width = New GridLength(MinPaddingRight)
         If EventType = "打开帮助" Then
             Try
                 Dim NewElem = New HelpEntry(GetEventAbsoluteUrls(EventData, EventType)(0)).SetToListItem(Me)

+ 30 - 62
Plain Craft Launcher 2/Controls/MyLoading.xaml.vb

@@ -67,32 +67,35 @@ Public Class MyLoading
     ''' </summary>
     Public Property TextErrorInherit As Boolean = True
 
-    Private Sub RefreshText() Handles Me.IsErrorChanged, Me.Loaded
-        If InnerState = MyLoadingState.Error Then
-            If TextErrorInherit AndAlso State.IsLoader Then
-                Dim Ex As Exception = CType(State, Object).Error
-                If Ex Is Nothing Then
-                    LabText.Text = "未知错误"
-                Else
-                    Do While Ex.InnerException IsNot Nothing
-                        Ex = Ex.InnerException
-                    Loop
-                    LabText.Text = StrTrim(Ex.Message)
-                    If {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
-                        "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) LabText.Text.Contains(s)) Then
-                        LabText.Text = "网络环境不佳,请重试或尝试使用 VPN"
+    Private Sub RefreshText() Handles Me.IsErrorChanged, Me.Loaded, _State.ProgressChanged
+        RunInUi(
+        Sub()
+            If InnerState = MyLoadingState.Error Then
+                If TextErrorInherit AndAlso State.IsLoader Then
+                    Dim Ex As Exception = CType(State, Object).Error
+                    If Ex Is Nothing Then
+                        LabText.Text = "未知错误"
+                    Else
+                        Do While Ex.InnerException IsNot Nothing
+                            Ex = Ex.InnerException
+                        Loop
+                        LabText.Text = StrTrim(Ex.Message)
+                        If {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
+                            "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) LabText.Text.Contains(s)) Then
+                            LabText.Text = "网络环境不佳,请重试或尝试使用 VPN"
+                        End If
                     End If
+                Else
+                    LabText.Text = TextError
                 End If
             Else
-                LabText.Text = TextError
-            End If
-        Else
-            If ShowProgress AndAlso State.IsLoader Then
-                LabText.Text = Text & " - " & Math.Floor(CType(State, Object).Progress * 100) & "%"
-            Else
-                LabText.Text = Text
+                If ShowProgress AndAlso State.IsLoader Then
+                    LabText.Text = Text & " - " & Math.Floor(CType(State, Object).Progress * 100) & "%"
+                Else
+                    LabText.Text = Text
+                End If
             End If
-        End If
+        End Sub)
     End Sub
 
 #End Region
@@ -183,46 +186,7 @@ Public Class MyLoading
         If Not HasAnimation OrElse IsLooping OrElse Not InnerState = MyLoadingState.Run OrElse AniSpeed > 10 OrElse Not IsLoaded Then Exit Sub
         IsLooping = True
         ErrorAnimationWaiting = True
-        If ShowProgress Then
-            RefreshText()
-            AniStart({
-                    AaRotateTransform(PathPickaxe, -20 - CType(PathPickaxe.RenderTransform, RotateTransform).Angle, 350, 250, New AniEaseInBack(AniEasePower.Weak)),
-                    AaCode(AddressOf RefreshText, 100), '这是为了省下一个 Timer 线程……
-                    AaCode(AddressOf RefreshText, 200),
-                    AaCode(AddressOf RefreshText, 300),
-                    AaCode(AddressOf RefreshText, 400),
-                    AaCode(AddressOf RefreshText, 500),
-                    AaRotateTransform(PathPickaxe, 50, 900,, New AniEaseOutFluent, True),
-                    AaRotateTransform(PathPickaxe, 25, 900,, New AniEaseOutElastic(AniEasePower.Weak)),
-                    AaCode(Sub()
-                               PathLeft.Opacity = 1
-                               PathLeft.Margin = New Thickness(7, 41, 0, 0)
-                               PathRight.Opacity = 1
-                               PathRight.Margin = New Thickness(14, 41, 0, 0)
-                               ErrorAnimationWaiting = False
-                               RefreshText()
-                           End Sub),
-                    AaCode(AddressOf RefreshText, 100),
-                    AaCode(AddressOf RefreshText, 200),
-                    AaCode(AddressOf RefreshText, 300),
-                    AaCode(AddressOf RefreshText, 400),
-                    AaCode(AddressOf RefreshText, 500),
-                    AaCode(AddressOf RefreshText, 600),
-                    AaCode(AddressOf RefreshText, 700),
-                    AaCode(AddressOf RefreshText, 800),
-                    AaOpacity(PathLeft, -1, 100, 70),
-                    AaX(PathLeft, -5, 180, 20, New AniEaseOutFluent),
-                    AaY(PathLeft, -6, 180, 20, New AniEaseOutFluent),
-                    AaOpacity(PathRight, -1, 100, 70),
-                    AaX(PathRight, 5, 180, 20, New AniEaseOutFluent),
-                    AaY(PathRight, -6, 180, 20, New AniEaseOutFluent),
-                    AaCode(Sub()
-                               IsLooping = False
-                               AniLoop()
-                           End Sub,, True)
-            }, "MyLoader Loop " & Uuid & "/" & GetUuid())
-        Else
-            AniStart({
+        AniStart({
                     AaRotateTransform(PathPickaxe, -20 - CType(PathPickaxe.RenderTransform, RotateTransform).Angle, 350, 250, New AniEaseInBack(AniEasePower.Weak)),
                     AaRotateTransform(PathPickaxe, 50, 900,, New AniEaseOutFluent, True),
                     AaRotateTransform(PathPickaxe, 25, 900,, New AniEaseOutElastic(AniEasePower.Weak)),
@@ -244,6 +208,8 @@ Public Class MyLoading
                                AniLoop()
                            End Sub,, True)
             }, "MyLoader Loop " & Uuid & "/" & GetUuid())
+        If ShowProgress Then
+
         End If
     End Sub
 
@@ -294,6 +260,7 @@ Public Interface ILoadingTrigger
     ReadOnly Property IsLoader As Boolean
     Property LoadingState As MyLoadingState
     Event LoadingStateChanged(NewState As MyLoadingState, OldState As MyLoadingState)
+    Event ProgressChanged(NewProgress As Double, OldProgress As Double)
 End Interface
 
 Public Class MyLoadingStateSimulator
@@ -313,4 +280,5 @@ Public Class MyLoadingStateSimulator
     Public ReadOnly Property IsLoader As Boolean = False Implements ILoadingTrigger.IsLoader
 
     Public Event LoadingStateChanged(NewState As MyLoadingState, OldState As MyLoadingState) Implements ILoadingTrigger.LoadingStateChanged
+    Public Event ProgressChanged(NewProgress As Double, OldProgress As Double) Implements ILoadingTrigger.ProgressChanged
 End Class

+ 23 - 11
Plain Craft Launcher 2/FormMain.xaml.vb

@@ -1,4 +1,5 @@
 Imports System.ComponentModel
+Imports System.Runtime.InteropServices
 
 Public Class FormMain
 
@@ -134,6 +135,13 @@ Public Class FormMain
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '1:BUG-
+        If LastVersion < 307 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
@@ -351,7 +359,6 @@ Public Class FormMain
     End Sub
 
     '窗口加载
-    Private Declare Auto Function ChangeWindowMessageFilterEx Lib "user32.dll" (ByVal hwnd As IntPtr, ByVal message As UInt32, ByVal action As UInt32, ByVal changeInfo As UInt32) As Boolean
     Private IsWindowLoadFinished As Boolean = False
     ''' <summary>
     ''' 是否为联机提权后自动重启。
@@ -489,20 +496,25 @@ Public Class FormMain
                            Catch ex As Exception
                                Log(ex, "清理自动更新文件失败")
                            End Try
-                           '开启管理员权限下的文件拖拽(#2531)
-                           Try
-                               If IsAdmin() Then
-                                   Log("[System] PCL 正以管理员权限运行")
-                                   ChangeWindowMessageFilterEx(Handle, &H233, 1, Nothing)
-                                   ChangeWindowMessageFilterEx(Handle, &H49, 1, Nothing)
-                               End If
-                           Catch ex As Exception
-                               Log(ex, "开启管理员权限下的文件拖拽失败")
-                           End Try
+                           '开启管理员权限下的文件拖拽,但下列代码也没用(#2531)
+                           If IsAdmin() Then
+                               Log("[System] PCL 正以管理员权限运行")
+                               Dim changeInfo As New ChangeFilter
+                               changeInfo.cbSize = Marshal.SizeOf(changeInfo)
+                               ChangeWindowMessageFilterEx(Handle, &H233, 1, changeInfo)
+                               ChangeWindowMessageFilterEx(Handle, &H4A, 1, changeInfo)
+                               ChangeWindowMessageFilterEx(Handle, &H49, 1, changeInfo)
+                           End If
                        End Sub, "Start Loader", ThreadPriority.Lowest)
 
         Log("[Start] 第三阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
     End Sub
+    <StructLayout(LayoutKind.Sequential)>
+    Public Structure ChangeFilter
+        Public cbSize As UInteger
+        Public ExtStatus As UInteger
+    End Structure
+    Private Declare Function ChangeWindowMessageFilterEx Lib "user32.dll" (hwnd As IntPtr, message As UInteger, action As UInteger, changeInfo As ChangeFilter) As Boolean
     '根据打开次数触发的事件
     Private Sub RunCountSub()
         Setup.Set("SystemCount", Setup.Get("SystemCount") + 1)

BIN
Plain Craft Launcher 2/Images/Icons/Disabled.png


BIN
Plain Craft Launcher 2/Images/Icons/NoIcon.png


BIN
Plain Craft Launcher 2/Images/Icons/Unavaliable.png


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

@@ -11,12 +11,12 @@ Public Module ModBase
 #Region "声明"
 
     '下列版本信息由更新器自动修改
-    Public Const VersionBaseName As String = "2.6.100" '不含分支前缀的显示用版本名
-    Public Const VersionStandardCode As String = "2.6.100." & VersionBranchCode '标准格式的四段式版本号
+    Public Const VersionBaseName As String = "2.6.11" '不含分支前缀的显示用版本名
+    Public Const VersionStandardCode As String = "2.6.11." & VersionBranchCode '标准格式的四段式版本号
 #If BETA Then
     Public Const VersionCode As Integer = 308 'Release
 #Else
-    Public Const VersionCode As Integer = 307 'Snapshot
+    Public Const VersionCode As Integer = 309 'Snapshot
 #End If
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -560,7 +560,7 @@ Public Module ModBase
     ''' <summary>
     ''' 重命名一个注册表子键。不可用于包含子键的子键。
     ''' </summary>
-    Public Sub RegRename(parentKey As Microsoft.Win32.RegistryKey, subKeyName As String, newSubKeyName As String)
+    Public Sub RenameReg(parentKey As Microsoft.Win32.RegistryKey, subKeyName As String, newSubKeyName As String)
         If parentKey.GetSubKeyNames().Contains(newSubKeyName) Then parentKey.DeleteSubKeyTree(newSubKeyName, False)
         Dim SourceKey As Microsoft.Win32.RegistryKey = parentKey.OpenSubKey(subKeyName)
         If IsNothing(SourceKey) Then Exit Sub '没有目标项
@@ -1062,7 +1062,7 @@ Public Module ModBase
     ''' <summary>
     ''' 获取文件 MD5,若失败则返回空字符串。
     ''' </summary>
-    Public Function GetAuthMD5(FilePath As String) As String
+    Public Function GetFileMD5(FilePath As String) As String
         Dim Retry As Boolean = False
 Re:
         Try
@@ -1091,7 +1091,7 @@ Re:
     ''' <summary>
     ''' 获取文件 SHA256,若失败则返回空字符串。
     ''' </summary>
-    Public Function GetAuthSHA256(FilePath As String) As String
+    Public Function GetFileSHA256(FilePath As String) As String
         Dim Retry As Boolean = False
 Re:
         Try
@@ -1122,7 +1122,7 @@ Re:
     ''' <summary>
     ''' 获取文件 SHA1,若失败则返回空字符串。
     ''' </summary>
-    Public Function GetAuthSHA1(FilePath As String) As String
+    Public Function GetFileSHA1(FilePath As String) As String
         Dim Retry As Boolean = False
 Re:
         Try
@@ -1210,11 +1210,11 @@ Re:
                 If MinSize >= 0 AndAlso MinSize > FileSize Then Return "文件大小应大于 " & MinSize & " B,实际为 " & FileSize & " B"
                 If Not String.IsNullOrEmpty(Hash) Then
                     If Hash.Length < 35 Then 'MD5
-                        If Hash.ToLowerInvariant <> GetAuthMD5(LocalPath) Then Return "文件 MD5 应为 " & Hash & ",实际为 " & GetAuthMD5(LocalPath)
+                        If Hash.ToLowerInvariant <> GetFileMD5(LocalPath) Then Return "文件 MD5 应为 " & Hash & ",实际为 " & GetFileMD5(LocalPath)
                     ElseIf Hash.Length = 64 Then 'SHA256
-                        If Hash.ToLowerInvariant <> GetAuthSHA256(LocalPath) Then Return "文件 SHA256 应为 " & Hash & ",实际为 " & GetAuthSHA256(LocalPath)
+                        If Hash.ToLowerInvariant <> GetFileSHA256(LocalPath) Then Return "文件 SHA256 应为 " & Hash & ",实际为 " & GetFileSHA256(LocalPath)
                     Else 'SHA1 (40)
-                        If Hash.ToLowerInvariant <> GetAuthSHA1(LocalPath) Then Return "文件 SHA1 应为 " & Hash & ",实际为 " & GetAuthSHA1(LocalPath)
+                        If Hash.ToLowerInvariant <> GetFileSHA1(LocalPath) Then Return "文件 SHA1 应为 " & Hash & ",实际为 " & GetFileSHA1(LocalPath)
                     End If
                 End If
                 If IsJson Then
@@ -2321,6 +2321,16 @@ Retry:
         Return Thread.CurrentThread.ManagedThreadId = UiThreadId
     End Function
 
+    ''' <summary>
+    ''' 检查某个控件是否位于主窗口可视区域内,且控件本身可见。
+    ''' </summary>
+    <Extension> Public Function IsVisibleInForm(element As FrameworkElement) As Boolean
+        If Not element.IsVisible Then Return False
+        Dim bounds As Rect = element.TransformToAncestor(FrmMain).TransformBounds(New Rect(0, 0, element.ActualWidth, element.ActualHeight))
+        Dim rect As New Rect(0, 0, FrmMain.ActualWidth, FrmMain.ActualHeight)
+        Return rect.Contains(bounds.TopLeft) OrElse rect.Contains(bounds.BottomRight)
+    End Function
+
 #End Region
 
 #Region "Debug"

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

@@ -153,10 +153,14 @@
                 End Select
             End Get
             Set(value As Double)
+                If _Progress = value Then Exit Property
+                Dim OldValue = _Progress
                 _Progress = value
+                RaiseEvent ProgressChanged(value, OldValue)
             End Set
         End Property
         Private _Progress As Double = -1
+        Public Event ProgressChanged(NewProgress As Double, OldProgress As Double) Implements ILoadingTrigger.ProgressChanged
         ''' <summary>
         ''' 计算总进度时的权重。它应该为预计时间(秒)。
         ''' </summary>
@@ -236,12 +240,16 @@
         ''' </summary>
         Public ReadOnly Property IsAborted As Boolean
             Get
-                Return LastRunningThread Is Nothing OrElse
-                       Not ReferenceEquals(Thread.CurrentThread, LastRunningThread) OrElse
-                       State = LoadState.Aborted
+                Return IsAbortedWithThread(Thread.CurrentThread)
             End Get
         End Property
         ''' <summary>
+        ''' 当前执行线程是否应当中断。需要手动提供加载器线程,用于需要跨线程检查的情况。
+        ''' </summary>
+        Public Function IsAbortedWithThread(Thread As Thread) As Boolean
+            Return LastRunningThread Is Nothing OrElse Not ReferenceEquals(Thread, LastRunningThread) OrElse State = LoadState.Aborted
+        End Function
+        ''' <summary>
         ''' 在输入相同时使用原有结果的超时,单位为毫秒。
         ''' </summary>
         Public ReloadTimeout As Integer = -1

+ 310 - 245
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb

@@ -4,15 +4,15 @@
         ''' <summary>
         ''' Mod。
         ''' </summary>
-        [Mod]
+        [Mod] = 0
         ''' <summary>
         ''' 整合包。
         ''' </summary>
-        ModPack
+        ModPack = 1
         ''' <summary>
         ''' 资源包。
         ''' </summary>
-        ResourcePack
+        ResourcePack = 2
     End Enum
     Public Enum CompModLoaderType
         'https://docs.curseforge.com/?http#tocS_ModLoaderType
@@ -164,7 +164,7 @@
         ''' <summary>
         ''' Logo 图片的下载地址。若为 Nothing 则没有。
         ''' </summary>
-        Public ReadOnly LogoUrl As String = Nothing
+        Public LogoUrl As String = Nothing
         ''' <summary>
         ''' 游戏大版本列表。例如:18, 16, 15……
         ''' </summary>
@@ -221,174 +221,224 @@
         ''' 从工程 Json 中初始化实例。若出错会抛出异常。
         ''' </summary>
         Public Sub New(Data As JObject)
-            FromCurseForge = Data.ContainsKey("gameId")
-            If FromCurseForge Then
-#Region "CurseForge"
-                '简单信息
-                Id = Data("id")
-                Slug = Data("slug")
-                RawName = Data("name")
-                Description = Data("summary")
-                Website = Data("links")("websiteUrl").ToString.TrimEnd("/")
-                LastUpdate = Data("dateReleased") '#1194
-                DownloadCount = Data("downloadCount")
-                'Logo
-                If Data("logo").Count > 0 Then
-                    If GetPixelSize(1) > 1.25 Then
-                        LogoUrl = Data("logo")("thumbnailUrl").ToString '使用 256x256 图标
-                    Else
-                        LogoUrl = Data("logo")("thumbnailUrl").ToString.Replace("/256/256/", "/64/64/") '使用 64x64 图标
-                    End If
+            If Data.ContainsKey("Tags") Then
+#Region "CompJson"
+                FromCurseForge = Data("DataSource") = "CurseForge"
+                Type = Data("Type").ToObject(Of Integer)
+                Slug = Data("Slug")
+                Id = Data("Id")
+                If Data.ContainsKey("CurseForgeFileIds") Then CurseForgeFileIds = CType(Data("CurseForgeFileIds"), JArray).Select(Function(t) t.ToObject(Of Integer)).ToList
+                RawName = Data("RawName")
+                Description = Data("Description")
+                Website = Data("Website")
+                If Data.ContainsKey("LastUpdate") Then LastUpdate = Data("LastUpdate")
+                DownloadCount = Data("DownloadCount")
+                If Data.ContainsKey("ModLoaders") Then
+                    ModLoaders = CType(Data("ModLoaders"), JArray).Select(Function(t) CType(t.ToObject(Of Integer), CompModLoaderType)).ToList
+                Else
+                    ModLoaders = New List(Of CompModLoaderType)
                 End If
-                'FileIndexes / GameVersions
-                Dim Files = If(CType(Data("latestFilesIndexes"), JArray), New JArray).
-                            Where(Function(i) i("gameVersion").ToString.Contains("1."))
-                CurseForgeFileIds = Files.Select(Of Integer)(Function(v) v("fileId")).ToList
-                GameVersions = Files.Select(Of Integer)(Function(v) Val(v("gameVersion").ToString.Split(".")(1).Split("-").First)).Where(Function(v) v > 0).
-                               Distinct.OrderByDescending(Function(v) v).ToList
-                'Type
-                If Website.Contains("/mc-mods/") OrElse Website.Contains("/mod/") Then
-                    Type = CompType.Mod
-                ElseIf Website.Contains("/modpacks/") Then
-                    Type = CompType.ModPack
+                Tags = CType(Data("Tags"), JArray).Select(Function(t) t.ToString).ToList
+                If Data.ContainsKey("LogoUrl") Then LogoUrl = Data("LogoUrl")
+                If Data.ContainsKey("GameVersions") Then
+                    GameVersions = CType(Data("GameVersions"), JArray).Select(Function(t) t.ToObject(Of Integer)).ToList
                 Else
-                    Type = CompType.ResourcePack
+                    GameVersions = New List(Of Integer)
                 End If
-                'Tags
-                Tags = New List(Of String)
-                For Each Category In Data("categories").Select(Of Integer)(Function(t) t("id")).Distinct.OrderByDescending(Function(c) c)
-                    Select Case Category
+#End Region
+            Else
+                FromCurseForge = Data.ContainsKey("summary")
+                If FromCurseForge Then
+#Region "CurseForge"
+                    '简单信息
+                    Id = Data("id")
+                    Slug = Data("slug")
+                    RawName = Data("name")
+                    Description = Data("summary")
+                    Website = Data("links")("websiteUrl").ToString.TrimEnd("/")
+                    LastUpdate = Data("dateReleased") '#1194
+                    DownloadCount = Data("downloadCount")
+                    'Logo
+                    If Data("logo").Count > 0 Then
+                        If GetPixelSize(1) > 1.25 Then
+                            LogoUrl = Data("logo")("thumbnailUrl").ToString '使用 256x256 图标
+                        Else
+                            LogoUrl = Data("logo")("thumbnailUrl").ToString.Replace("/256/256/", "/64/64/") '使用 64x64 图标
+                        End If
+                    End If
+                    'FileIndexes / GameVersions
+                    Dim Files = If(CType(Data("latestFilesIndexes"), JArray), New JArray).
+                                Where(Function(i) i("gameVersion").ToString.Contains("1."))
+                    CurseForgeFileIds = Files.Select(Of Integer)(Function(v) v("fileId")).Distinct.ToList
+                    GameVersions = Files.Select(Of Integer)(Function(v) Val(v("gameVersion").ToString.Split(".")(1).Split("-").First)).Where(Function(v) v > 0).
+                                   Distinct.OrderByDescending(Function(v) v).ToList
+                    'Type
+                    If Website.Contains("/mc-mods/") OrElse Website.Contains("/mod/") Then
+                        Type = CompType.Mod
+                    ElseIf Website.Contains("/modpacks/") Then
+                        Type = CompType.ModPack
+                    Else
+                        Type = CompType.ResourcePack
+                    End If
+                    'Tags
+                    Tags = New List(Of String)
+                    For Each Category In Data("categories").Select(Of Integer)(Function(t) t("id")).Distinct.OrderByDescending(Function(c) c)
+                        Select Case Category
                         'Mod
-                        Case 406 : Tags.Add("世界元素")
-                        Case 407 : Tags.Add("生物群系")
-                        Case 410 : Tags.Add("维度")
-                        Case 408 : Tags.Add("矿物/资源")
-                        Case 409 : Tags.Add("天然结构")
-                        Case 412 : Tags.Add("科技")
-                        Case 415 : Tags.Add("管道/物流")
-                        Case 4843 : Tags.Add("自动化")
-                        Case 417 : Tags.Add("能源")
-                        Case 4558 : Tags.Add("红石")
-                        Case 436 : Tags.Add("食物/烹饪")
-                        Case 416 : Tags.Add("农业")
-                        Case 414 : Tags.Add("运输")
-                        Case 420 : Tags.Add("仓储")
-                        Case 419 : Tags.Add("魔法")
-                        Case 422 : Tags.Add("冒险")
-                        Case 424 : Tags.Add("装饰")
-                        Case 411 : Tags.Add("生物")
-                        Case 434 : Tags.Add("装备")
-                        Case 423 : Tags.Add("信息显示")
-                        Case 435 : Tags.Add("服务器")
-                        Case 5191 : Tags.Add("改良")
-                        Case 421 : Tags.Add("支持库")
+                            Case 406 : Tags.Add("世界元素")
+                            Case 407 : Tags.Add("生物群系")
+                            Case 410 : Tags.Add("维度")
+                            Case 408 : Tags.Add("矿物/资源")
+                            Case 409 : Tags.Add("天然结构")
+                            Case 412 : Tags.Add("科技")
+                            Case 415 : Tags.Add("管道/物流")
+                            Case 4843 : Tags.Add("自动化")
+                            Case 417 : Tags.Add("能源")
+                            Case 4558 : Tags.Add("红石")
+                            Case 436 : Tags.Add("食物/烹饪")
+                            Case 416 : Tags.Add("农业")
+                            Case 414 : Tags.Add("运输")
+                            Case 420 : Tags.Add("仓储")
+                            Case 419 : Tags.Add("魔法")
+                            Case 422 : Tags.Add("冒险")
+                            Case 424 : Tags.Add("装饰")
+                            Case 411 : Tags.Add("生物")
+                            Case 434 : Tags.Add("装备")
+                            Case 423 : Tags.Add("信息显示")
+                            Case 435 : Tags.Add("服务器")
+                            Case 5191 : Tags.Add("改良")
+                            Case 421 : Tags.Add("支持库")
                         '整合包
-                        Case 4484 : Tags.Add("多人")
-                        Case 4479 : Tags.Add("硬核")
-                        Case 4483 : Tags.Add("战斗")
-                        Case 4478 : Tags.Add("任务")
-                        Case 4472 : Tags.Add("科技")
-                        Case 4473 : Tags.Add("魔法")
-                        Case 4475 : Tags.Add("冒险")
-                        Case 4476 : Tags.Add("探索")
-                        Case 4477 : Tags.Add("小游戏")
-                        Case 4471 : Tags.Add("科幻")
-                        Case 4736 : Tags.Add("空岛")
-                        Case 5128 : Tags.Add("原版改良")
-                        Case 4487 : Tags.Add("FTB")
-                        Case 4480 : Tags.Add("基于地图")
-                        Case 4481 : Tags.Add("轻量")
-                        Case 4482 : Tags.Add("大型")
-                            'FUTURE: Res
-                    End Select
-                Next
-                If Tags.Count = 0 Then Tags.Add("杂项")
-                'ModLoaders
-                ModLoaders = New List(Of CompModLoaderType)
-                For Each File In If(Data("latestFiles"), {})
-                    Dim NewFile As New CompFile(File, Type)
-                    If Not NewFile.Available Then Continue For
-                    ModLoaders.AddRange(NewFile.ModLoaders)
-                Next
-                ModLoaders = ModLoaders.Distinct.OrderBy(Of Integer)(Function(t) t).ToList
+                            Case 4484 : Tags.Add("多人")
+                            Case 4479 : Tags.Add("硬核")
+                            Case 4483 : Tags.Add("战斗")
+                            Case 4478 : Tags.Add("任务")
+                            Case 4472 : Tags.Add("科技")
+                            Case 4473 : Tags.Add("魔法")
+                            Case 4475 : Tags.Add("冒险")
+                            Case 4476 : Tags.Add("探索")
+                            Case 4477 : Tags.Add("小游戏")
+                            Case 4471 : Tags.Add("科幻")
+                            Case 4736 : Tags.Add("空岛")
+                            Case 5128 : Tags.Add("原版改良")
+                            Case 4487 : Tags.Add("FTB")
+                            Case 4480 : Tags.Add("基于地图")
+                            Case 4481 : Tags.Add("轻量")
+                            Case 4482 : Tags.Add("大型")
+                                'FUTURE: Res
+                        End Select
+                    Next
+                    If Tags.Count = 0 Then Tags.Add("杂项")
+                    'ModLoaders
+                    ModLoaders = New List(Of CompModLoaderType)
+                    For Each File In If(Data("latestFiles"), {})
+                        Dim NewFile As New CompFile(File, Type)
+                        If Not NewFile.Available Then Continue For
+                        ModLoaders.AddRange(NewFile.ModLoaders)
+                    Next
+                    ModLoaders = ModLoaders.Distinct.OrderBy(Of Integer)(Function(t) t).ToList
 #End Region
-            Else
+                Else
 #Region "Modrinth"
-                '简单信息
-                Id = If(Data("project_id"), Data("id")) '两个 API 会返回的 key 不一样
-                Slug = Data("slug")
-                RawName = Data("title")
-                Description = Data("description")
-                LastUpdate = Data("date_modified")
-                DownloadCount = Data("downloads")
-                LogoUrl = Data("icon_url")
-                If LogoUrl = "" Then LogoUrl = Nothing
-                Website = $"https://modrinth.com/{Data("project_type")}/{Slug}"
-                'GameVersions
-                GameVersions = If(CType(Data("versions"), JArray), New JArray).
-                                   Select(Function(v) v.ToString).Where(Function(v) v.StartsWith("1.")).
-                                   Select(Of Integer)(Function(v) Val(v.Split(".")(1).Split("-").First)).Where(Function(v) v > 0).
-                                   Distinct.OrderByDescending(Function(v) v).ToList
-                'Type
-                Select Case Data("project_type").ToString
-                    Case "mod" : Type = CompType.Mod
-                    Case "modpack" : Type = CompType.ModPack
-                    Case "resourcepack" : Type = CompType.ResourcePack
-                End Select
-                'Tags & ModLoaders
-                Tags = New List(Of String)
-                ModLoaders = New List(Of CompModLoaderType)
-                For Each Category In Data("categories").Select(Function(t) t.ToString)
-                    Select Case Category
+                    '简单信息
+                    Id = If(Data("project_id"), Data("id")) '两个 API 会返回的 key 不一样
+                    Slug = Data("slug")
+                    RawName = Data("title")
+                    Description = Data("description")
+                    LastUpdate = Data("date_modified")
+                    DownloadCount = Data("downloads")
+                    LogoUrl = Data("icon_url")
+                    If LogoUrl = "" Then LogoUrl = Nothing
+                    Website = $"https://modrinth.com/{Data("project_type")}/{Slug}"
+                    'GameVersions
+                    '搜索结果的键为 versions,获取特定工程的键为 game_versions
+                    GameVersions = If(CType(If(Data("game_versions"), Data("versions")), JArray), New JArray).
+                                       Select(Function(v) v.ToString).Where(Function(v) v.StartsWith("1.")).
+                                       Select(Of Integer)(Function(v) Val(v.Split(".")(1).Split("-").First)).Where(Function(v) v > 0).
+                                       Distinct.OrderByDescending(Function(v) v).ToList
+                    'Type
+                    Select Case Data("project_type").ToString
+                        Case "mod" : Type = CompType.Mod
+                        Case "modpack" : Type = CompType.ModPack
+                        Case "resourcepack" : Type = CompType.ResourcePack
+                    End Select
+                    'Tags & ModLoaders
+                    Tags = New List(Of String)
+                    ModLoaders = New List(Of CompModLoaderType)
+                    For Each Category In Data("categories").Select(Function(t) t.ToString)
+                        Select Case Category
                         '加载器
-                        Case "forge" : ModLoaders.Add(CompModLoaderType.Forge)
-                        Case "fabric" : ModLoaders.Add(CompModLoaderType.Fabric)
-                        Case "quilt" : ModLoaders.Add(CompModLoaderType.Quilt)
+                            Case "forge" : ModLoaders.Add(CompModLoaderType.Forge)
+                            Case "fabric" : ModLoaders.Add(CompModLoaderType.Fabric)
+                            Case "quilt" : ModLoaders.Add(CompModLoaderType.Quilt)
                         'Mod
-                        Case "worldgen" : Tags.Add("世界元素")
-                        Case "technology" : Tags.Add("科技")
-                        Case "food" : Tags.Add("食物/烹饪")
-                        Case "game-mechanics" : Tags.Add("游戏机制")
-                        Case "transportation" : Tags.Add("运输")
-                        Case "storage" : Tags.Add("仓储")
-                        Case "magic" : Tags.Add("魔法")
-                        Case "adventure" : Tags.Add("冒险")
-                        Case "decoration" : Tags.Add("装饰")
-                        Case "mobs" : Tags.Add("生物")
-                        Case "equipment" : Tags.Add("装备")
-                        Case "optimization" : Tags.Add("性能优化")
-                        Case "social" : Tags.Add("服务器")
-                        Case "utility" : Tags.Add("改良")
-                        Case "library" : Tags.Add("支持库")
+                            Case "worldgen" : Tags.Add("世界元素")
+                            Case "technology" : Tags.Add("科技")
+                            Case "food" : Tags.Add("食物/烹饪")
+                            Case "game-mechanics" : Tags.Add("游戏机制")
+                            Case "transportation" : Tags.Add("运输")
+                            Case "storage" : Tags.Add("仓储")
+                            Case "magic" : Tags.Add("魔法")
+                            Case "adventure" : Tags.Add("冒险")
+                            Case "decoration" : Tags.Add("装饰")
+                            Case "mobs" : Tags.Add("生物")
+                            Case "equipment" : Tags.Add("装备")
+                            Case "optimization" : Tags.Add("性能优化")
+                            Case "social" : Tags.Add("服务器")
+                            Case "utility" : Tags.Add("改良")
+                            Case "library" : Tags.Add("支持库")
                         '整合包
-                        Case "multiplayer" : Tags.Add("多人")
-                        Case "optimization" : Tags.Add("性能优化")
-                        Case "challenging" : Tags.Add("硬核")
-                        Case "combat" : Tags.Add("战斗")
-                        Case "quests" : Tags.Add("任务")
-                        Case "technology" : Tags.Add("科技")
-                        Case "magic" : Tags.Add("魔法")
-                        Case "adventure" : Tags.Add("冒险")
-                        Case "kitchen-sink" : Tags.Add("烹饪")
-                        Case "lightweight" : Tags.Add("轻量")
-                            'FUTURE: Res
-                    End Select
-                Next
-                If Tags.Count = 0 Then Tags.Add("杂项")
-                Tags.Sort()
-                ModLoaders.Sort()
+                            Case "multiplayer" : Tags.Add("多人")
+                            Case "optimization" : Tags.Add("性能优化")
+                            Case "challenging" : Tags.Add("硬核")
+                            Case "combat" : Tags.Add("战斗")
+                            Case "quests" : Tags.Add("任务")
+                            Case "technology" : Tags.Add("科技")
+                            Case "magic" : Tags.Add("魔法")
+                            Case "adventure" : Tags.Add("冒险")
+                            Case "kitchen-sink" : Tags.Add("烹饪")
+                            Case "lightweight" : Tags.Add("轻量")
+                                'FUTURE: Res
+                        End Select
+                    Next
+                    If Tags.Count = 0 Then Tags.Add("杂项")
+                    Tags.Sort()
+                    ModLoaders.Sort()
 #End Region
+                End If
             End If
             '保存缓存
             CompProjectCache(Id) = Me
         End Sub
         ''' <summary>
+        ''' 将当前实例转为可用于保存缓存的 Json。
+        ''' </summary>
+        Public Function ToJson() As JObject
+            Dim Json As New JObject
+            Json("DataSource") = If(FromCurseForge, "CurseForge", "Modrinth")
+            Json("Type") = CInt(Type)
+            Json("Slug") = Slug
+            Json("Id") = Id
+            If CurseForgeFileIds IsNot Nothing Then Json("CurseForgeFileIds") = New JArray(CurseForgeFileIds)
+            Json("RawName") = RawName
+            Json("Description") = Description
+            Json("Website") = Website
+            If LastUpdate IsNot Nothing Then Json("LastUpdate") = LastUpdate
+            Json("DownloadCount") = DownloadCount
+            If ModLoaders IsNot Nothing AndAlso ModLoaders.Count > 0 Then Json("ModLoaders") = New JArray(ModLoaders.Select(Function(m) CInt(m)))
+            Json("Tags") = New JArray(Tags)
+            If LogoUrl IsNot Nothing Then Json("LogoUrl") = LogoUrl
+            If GameVersions.Count > 0 Then Json("GameVersions") = New JArray(GameVersions)
+            Json("CacheTime") = Date.Now '用于检查缓存时间
+            Return Json
+        End Function
+        ''' <summary>
         ''' 将当前工程信息实例化为控件。
         ''' </summary>
         Public Function ToCompItem(ShowMcVersionDesc As Boolean, ShowLoaderDesc As Boolean) As MyCompItem
             '获取版本描述
             Dim GameVersionDescription As String
-            If GameVersions Is Nothing OrElse GameVersions.Count = 0 Then
+            If GameVersions Is Nothing OrElse Not GameVersions.Any() Then
                 GameVersionDescription = "未知"
             Else
                 Dim SpaVersions As New List(Of String)
@@ -418,94 +468,16 @@
                 GameVersionDescription = SpaVersions.Join(", ")
             End If
             '实例化 UI
-            Dim NewItem As New MyCompItem With {.Tag = Me}
-            '整理 FinalName 与 ExNameList
-            '检查下列代码时可以参考 #1567 的测试例
-            Dim FinalName As String = RawName
-            Dim ExNameList As List(Of String)
-            If TranslatedName = RawName Then
-                '没有中文翻译
-                '将所有名称分段
-                Dim NameLists = TranslatedName.Split({" | ", " - ", "(", ")", "[", "]", "{", "}"}, StringSplitOptions.RemoveEmptyEntries).
-                    Select(Function(s) s.Trim(" /\".ToCharArray)).Where(Function(w) Not String.IsNullOrEmpty(w)).ToList
-                If NameLists.Count = 1 Then GoTo NoExName
-                '查找其中的缩写、Forge/Fabric 等版本标记
-                ExNameList = New List(Of String)
-                Dim NormalNameList = New List(Of String)
-                For Each Name In NameLists
-                    Dim LowerName As String = Name.ToLower
-                    If Name.ToUpper = Name AndAlso Name <> "FPS" AndAlso Name <> "HUD" Then
-                        '缩写
-                        ExNameList.Add(Name)
-                    ElseIf (LowerName.Contains("forge") OrElse LowerName.Contains("fabric") OrElse LowerName.Contains("quilt")) AndAlso
-                        Not RegexCheck(LowerName.Replace("forge", "").Replace("fabric", "").Replace("quilt", ""), "[a-z]+") Then '去掉关键词后没有其他字母
-                        'Forge/Fabric 等版本标记
-                        ExNameList.Add(Name)
-                    Else
-                        '其他部分
-                        NormalNameList.Add(Name)
-                    End If
-                Next
-                '根据分类后的结果处理
-                If NormalNameList.Count = 0 OrElse ExNameList.Count = 0 Then GoTo NoExName
-                '同时包含 NormalName 和 ExName
-                FinalName = NormalNameList.Join(" - ")
-                ExNameList = ExNameList
-            Else
-                '有中文翻译
-                '尝试将文本分为三段:FinalName (EnglishName) - Suffix
-                '检查时注意 Carpet:它没有中文译名,但有 Suffix
-                FinalName = TranslatedName.Split(" (").First.Split(" - ").First
-                Dim Suffix As String = ""
-                If TranslatedName.Split(")").Last.Contains(" - ") Then Suffix = TranslatedName.Split(")").Last.Split(" - ").Last
-                Dim EnglishName As String = TranslatedName
-                If Suffix <> "" Then EnglishName = EnglishName.Replace(" - " & Suffix, "")
-                EnglishName = EnglishName.Replace(FinalName, "").Trim("("c, ")"c, " "c)
-                '中段的额外信息截取
-                ExNameList = EnglishName.Split({" | ", " - ", "(", ")", "[", "]", "{", "}"}, StringSplitOptions.RemoveEmptyEntries).
-                        Select(Function(s) s.Trim(" /".ToCharArray)).Where(Function(w) Not String.IsNullOrEmpty(w)).ToList
-                If ExNameList.Count > 1 AndAlso
-                   Not ExNameList.Any(Function(s) s.ToLower.Contains("forge") OrElse s.ToLower.Contains("fabric") OrElse s.ToLower.Contains("quilt")) AndAlso '不是标注 XX 版
-                   Not (ExNameList.Count = 2 AndAlso ExNameList.Last.ToUpper = ExNameList.Last) Then '不是缩写
-                    ExNameList = New List(Of String) From {EnglishName} '使用原名
-                End If
-                '添加后缀
-                If Suffix <> "" Then ExNameList.Add(Suffix)
-            End If
-            ExNameList = ExNameList.Distinct().ToList()
-            '显示 FinalName 与 ExNameList
-            If ExNameList.Count = 0 Then
-NoExName:
-                NewItem.LabTitle.Text = FinalName
+            Dim NewItem As New MyCompItem With {.Tag = Me, .Logo = GetControlLogo()}
+            Dim Title = GetControlTitle(True)
+            NewItem.Title = Title.Key
+            If Title.Value = "" Then
                 CType(NewItem.LabTitleRaw.Parent, StackPanel).Children.Remove(NewItem.LabTitleRaw)
             Else
-                NewItem.LabTitle.Text = FinalName
-                Dim ExName As String = ""
-                For Each Ex In ExNameList
-                    '去除 “Forge/Fabric” 这一无意义提示
-                    If Ex.Length < 16 AndAlso Ex.ToLower.Contains("fabric") AndAlso Ex.ToLower.Contains("forge") Then Continue For
-                    '将 “Forge” 等提示改为 “Forge 版”
-                    If (Ex.ToLower.Contains("forge") OrElse Ex.ToLower.Contains("fabric") OrElse Ex.ToLower.Contains("quilt")) AndAlso Not Ex.Contains("版") AndAlso
-                        Ex.ToLower.Replace("forge", "").Replace("fabric", "").Replace("quilt", "").Length <= 3 Then
-                        Ex = Ex.Replace("Edition", "").Replace("edition", "").Trim.Capitalize & " 版"
-                    End If
-                    '将 “forge” 等词语的首字母大写
-                    Ex = Ex.Replace("forge", "Forge").Replace("neo", "Neo").Replace("fabric", "Fabric").Replace("quilt", "Quilt")
-                    ExName &= "  |  " & Ex.Trim
-                Next
-                If ExName = "" Then GoTo NoExName
-                NewItem.LabTitleRaw.Text = ExName
+                NewItem.SubTitle = Title.Value
             End If
-            'Tag
-            For Each TagText In Tags
-                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"" 
-                         SnapsToDevicePixels=""True"" UseLayoutRounding=""False"">
-                   <TextBlock Text=""" & TagText & """ Foreground=""#868686"" FontSize=""11"" />
-                </Border>")
-                NewItem.PanTags.Children.Add(NewTag)
-            Next
+            NewItem.Tags = Tags
+            NewItem.Description = Description.Replace(vbCr, "").Replace(vbLf, "")
             '下边栏
             If Not ShowMcVersionDesc AndAlso Not ShowLoaderDesc Then
                 '全部隐藏
@@ -548,7 +520,6 @@ NoExName:
                         End If
                 End Select
             End If
-            NewItem.LabInfo.Text = Description.Replace(vbCr, "").Replace(vbLf, "")
             NewItem.LabSource.Text = If(FromCurseForge, "CurseForge", "Modrinth")
             If LastUpdate IsNot Nothing Then
                 NewItem.LabTime.Text = GetTimeSpanString(LastUpdate - Date.Now, True)
@@ -561,13 +532,92 @@ NoExName:
             NewItem.LabDownload.Text =
                 If(DownloadCount > 100000000, Math.Round(DownloadCount / 100000000, 2) & " 亿",
                     If(DownloadCount > 100000, Math.Floor(DownloadCount / 10000) & " 万", DownloadCount))
-            '其他
+            Return NewItem
+        End Function
+        Public Function GetControlLogo() As String
             If LogoUrl Is Nothing Then
-                NewItem.Logo = "pack://application:,,,/images/Icons/NoIcon.png"
+                Return "pack://application:,,,/images/Icons/NoIcon.png"
             Else
-                NewItem.Logo = LogoUrl
+                Return LogoUrl
             End If
-            Return NewItem
+        End Function
+        Public Function GetControlTitle(HasModLoaderDescription As Boolean) As KeyValuePair(Of String, String)
+            '检查下列代码时可以参考 #1567 的测试例
+            Dim Title As String = RawName
+            Dim SubtitleList As List(Of String)
+            If TranslatedName = RawName Then
+                '没有中文翻译
+                '将所有名称分段
+                Dim NameLists = TranslatedName.Split({" | ", " - ", "(", ")", "[", "]", "{", "}"}, StringSplitOptions.RemoveEmptyEntries).
+                    Select(Function(s) s.Trim(" /\".ToCharArray)).Where(Function(w) Not String.IsNullOrEmpty(w)).ToList
+                If NameLists.Count = 1 Then GoTo NoSubtitle
+                '查找其中的缩写、Forge/Fabric 等版本标记
+                SubtitleList = New List(Of String)
+                Dim NormalNameList = New List(Of String)
+                For Each Name In NameLists
+                    Dim LowerName As String = Name.ToLower
+                    If Name.ToUpper = Name AndAlso Name <> "FPS" AndAlso Name <> "HUD" Then
+                        '缩写
+                        SubtitleList.Add(Name)
+                    ElseIf (LowerName.Contains("forge") OrElse LowerName.Contains("fabric") OrElse LowerName.Contains("quilt")) AndAlso
+                        Not RegexCheck(LowerName.Replace("forge", "").Replace("fabric", "").Replace("quilt", ""), "[a-z]+") Then '去掉关键词后没有其他字母
+                        'Forge/Fabric 等版本标记
+                        SubtitleList.Add(Name)
+                    Else
+                        '其他部分
+                        NormalNameList.Add(Name)
+                    End If
+                Next
+                '根据分类后的结果处理
+                If NormalNameList.Count = 0 OrElse SubtitleList.Count = 0 Then GoTo NoSubtitle
+                '同时包含 NormalName 和 Subtitle
+                Title = NormalNameList.Join(" - ")
+            Else
+                '有中文翻译
+                '尝试将文本分为三段:Title (EnglishName) - Suffix
+                '检查时注意 Carpet:它没有中文译名,但有 Suffix
+                Title = TranslatedName.Split(" (").First.Split(" - ").First
+                Dim Suffix As String = ""
+                If TranslatedName.Split(")").Last.Contains(" - ") Then Suffix = TranslatedName.Split(")").Last.Split(" - ").Last
+                Dim EnglishName As String = TranslatedName
+                If Suffix <> "" Then EnglishName = EnglishName.Replace(" - " & Suffix, "")
+                EnglishName = EnglishName.Replace(Title, "").Trim("("c, ")"c, " "c)
+                '中段的额外信息截取
+                SubtitleList = EnglishName.Split({" | ", " - ", "(", ")", "[", "]", "{", "}"}, StringSplitOptions.RemoveEmptyEntries).
+                        Select(Function(s) s.Trim(" /".ToCharArray)).Where(Function(w) Not String.IsNullOrEmpty(w)).ToList
+                If SubtitleList.Count > 1 AndAlso
+                   Not SubtitleList.Any(Function(s) s.ToLower.Contains("forge") OrElse s.ToLower.Contains("fabric") OrElse s.ToLower.Contains("quilt")) AndAlso '不是标注 XX 版
+                   Not (SubtitleList.Count = 2 AndAlso SubtitleList.Last.ToUpper = SubtitleList.Last) Then '不是缩写
+                    SubtitleList = New List(Of String) From {EnglishName} '使用原名
+                End If
+                '添加后缀
+                If Suffix <> "" Then SubtitleList.Add(Suffix)
+            End If
+            SubtitleList = SubtitleList.Distinct().ToList()
+            '设置标题与描述
+            Dim Subtitle As String = ""
+            If SubtitleList.Count > 0 Then
+                For Each Ex In SubtitleList
+                    Dim IsModLoaderDescription As Boolean =
+                        Ex.ToLower.Contains("forge") OrElse Ex.ToLower.Contains("fabric") OrElse Ex.ToLower.Contains("quilt")
+                    '是否显示 ModLoader 信息
+                    If Not HasModLoaderDescription AndAlso IsModLoaderDescription Then Continue For
+                    '去除 “Forge/Fabric” 这一无意义提示
+                    If Ex.Length < 16 AndAlso Ex.ToLower.Contains("fabric") AndAlso Ex.ToLower.Contains("forge") Then Continue For
+                    '将 “Forge” 等提示改为 “Forge 版”
+                    If IsModLoaderDescription AndAlso Not Ex.Contains("版") AndAlso
+                        Ex.ToLower.Replace("forge", "").Replace("fabric", "").Replace("quilt", "").Length <= 3 Then
+                        Ex = Ex.Replace("Edition", "").Replace("edition", "").Trim.Capitalize & " 版"
+                    End If
+                    '将 “forge” 等词语的首字母大写
+                    Ex = Ex.Replace("forge", "Forge").Replace("neo", "Neo").Replace("fabric", "Fabric").Replace("quilt", "Quilt")
+                    Subtitle &= "  |  " & Ex.Trim
+                Next
+            Else
+NoSubtitle:
+                Subtitle = ""
+            End If
+            Return New KeyValuePair(Of String, String)(Title, Subtitle)
         End Function
 
         '辅助函数
@@ -761,6 +811,10 @@ NoExName:
         ''' 可供展示的所有工程的列表。
         ''' </summary>
         Public Results As New List(Of CompProject)
+        ''' <summary>
+        ''' 当前的错误信息。如果没有则为 Nothing。
+        ''' </summary>
+        Public ErrorMessage As String = Nothing
 
     End Class
 
@@ -770,7 +824,7 @@ NoExName:
     ''' <summary>
     ''' 已知工程信息的缓存。
     ''' </summary>
-    Private CompProjectCache As New Dictionary(Of String, CompProject)
+    Public CompProjectCache As New Dictionary(Of String, CompProject)
     ''' <summary>
     ''' 根据搜索请求获取一系列的工程列表。需要基于加载器运行。
     ''' </summary>
@@ -887,6 +941,7 @@ Retry:
 
             '启动 CurseForge 线程
             Dim CurseForgeUrl As String = Task.Input.GetCurseForgeAddress()
+            Dim CurseForgeFailed As Boolean = False
             If CurseForgeUrl IsNot Nothing Then
                 CurseForgeThread = RunInNewThread(
                     Sub()
@@ -910,12 +965,14 @@ Retry:
                             Log(ex, "从 CurseForge 获取工程列表失败")
                             Storage.CurseForgeTotal = Storage.CurseForgeOffset
                             [Error] = ex
+                            CurseForgeFailed = True
                         End Try
                     End Sub, "CurseForge Project Request")
             End If
 
             '启动 Modrinth 线程
             Dim ModrinthUrl As String = Task.Input.GetModrinthAddress()
+            Dim ModrinthFailed As Boolean = False
             If ModrinthUrl IsNot Nothing Then
                 ModrinthThread = RunInNewThread(
                     Sub()
@@ -941,6 +998,7 @@ Retry:
                             Log(ex, "从 Modrinth 获取工程列表失败")
                             Storage.ModrinthTotal = Storage.ModrinthOffset
                             [Error] = ex
+                            ModrinthFailed = True
                         End Try
                     End Sub, "Modrinth Project Request")
             End If
@@ -951,7 +1009,8 @@ Retry:
             If ModrinthThread IsNot Nothing Then ModrinthThread.Join()
             If Task.IsAborted Then Exit Sub
 
-            '确认存在结果
+            '确保存在结果
+            Storage.ErrorMessage = Nothing
             If RawResults.Count = 0 Then
                 If [Error] IsNot Nothing Then
                     Throw [Error]
@@ -966,6 +1025,13 @@ Retry:
                         Throw New Exception("没有符合条件的结果")
                     End If
                 End If
+            ElseIf [Error] IsNot Nothing Then
+                '有结果但是有错误
+                If CurseForgeFailed Then
+                    Storage.ErrorMessage = $"无法连接到 CurseForge,所以目前仅显示了来自 Modrinth 的内容,结果可能不全。{vbCrLf}请尝试使用 VPN 或加速器以改善网络。"
+                Else
+                    Storage.ErrorMessage = $"无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,结果可能不全。{vbCrLf}请尝试使用 VPN 或加速器以改善网络。"
+                End If
             End If
 
         Finally
@@ -1160,7 +1226,7 @@ Retry:
                 FileName = Data("fileName")
                 'DownloadAddress
                 Dim Url = Data("downloadUrl").ToString
-                If Url = "" Then Url = "https://media.forgecdn.net/files/" & Id.ToString.Substring(0, 4) & "/" & Id.ToString.Substring(4).TrimStart("0") & "/" & FileName
+                If Url = "" Then Url = $"https://media.forgecdn.net/files/{CInt(Id.ToString.Substring(0, 4))}/{CInt(Id.ToString.Substring(4))}/{FileName}"
                 Url = Url.Replace(FileName, Net.WebUtility.UrlEncode(FileName)) '对文件名进行编码
                 DownloadAddress = {Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge", "://media"),
                                    Url.Replace("-service.overwolf.wtf", ".forgecdn.net"),
@@ -1282,7 +1348,6 @@ Retry:
                 ToolTipService.SetHorizontalOffset(BtnSave, 2)
                 AddHandler BtnSave.Click, OnSaveClick
                 NewItem.Buttons = {BtnSave}
-                NewItem.PaddingRight = 35
             End If
 
             '结束

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

@@ -641,7 +641,7 @@ Done:
             'If Not Stack.Contains(".") Then Continue For
             For Each IgnoreStack In {
                 "java", "sun", "javax", "jdk", "oolloo",
-                "org.lwjgl", "com.sun", "net.minecraftforge", "com.mojang", "net.minecraft", "cpw.mods", "com.google", "org.apache", "org.spongepowered", "net.fabricmc", "com.mumfrey",
+                "org.lwjgl", "com.sun", "net.minecraftforge", "paulscode.sound", "com.mojang", "net.minecraft", "cpw.mods", "com.google", "org.apache", "org.spongepowered", "net.fabricmc", "com.mumfrey",
                 "com.electronwill.nightconfig", "it.unimi.dsi",
                 "MojangTricksIntelDriversForPerformance_javaw"}
                 If Stack.StartsWith(IgnoreStack) Then GoTo NextStack
@@ -666,7 +666,7 @@ NextStack:
                 If {"com", "org", "net", "asm", "fml", "mod", "jar", "sun", "lib", "map", "gui", "dev", "nio", "api", "dsi", "top",
                     "core", "init", "mods", "main", "file", "game", "load", "read", "done", "util", "tile", "item", "base", "oshi",
                     "forge", "setup", "block", "model", "mixin", "event", "unimi", "netty",
-                    "common", "server", "config", "loader", "launch", "entity", "assist", "client", "plugin", "modapi", "mojang", "shader", "events", "github", "recipe",
+                    "gitlab", "common", "server", "config", "loader", "launch", "entity", "assist", "client", "plugin", "modapi", "mojang", "shader", "events", "github", "recipe",
                     "preinit", "preload", "machine", "reflect", "channel", "general", "handler", "content", "systems", "modules",
                     "fastutil", "optifine", "internal", "platform", "override",
                     "listeners", "scheduler", "minecraft", "transformers", "universal", "multipart", "minecraftforge", "blockentity"

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

@@ -455,7 +455,7 @@
     ''' </summary>
     Public DlForgeListOfficialLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Official", AddressOf DlForgeListOfficialMain)
     Private Sub DlForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
-        Dim Result As String = NetGetCodeByRequestRetry("http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding.Default, "text/html")
+        Dim Result As String = NetGetCodeByRequestRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding.Default, "text/html")
         If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
         '获取所有版本信息
         Dim Names As List(Of String) = RegexSearch(Result, "(?<=a href=""index_)[0-9.]+(_pre[0-9]?)?(?=.html)")
@@ -593,7 +593,7 @@
     Public Sub DlForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
         Dim Result As String
         Try
-            Result = NetGetCodeByDownload("http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" & Loader.Input & ".html")
+            Result = NetGetCodeByDownload("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" & Loader.Input & ".html")
         Catch ex As Exception
             If GetExceptionSummary(ex).Contains("(404)") Then
                 Throw New Exception("没有可用版本")
@@ -799,7 +799,7 @@
     ''' </summary>
     Public DlLiteLoaderListOfficialLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Official", AddressOf DlLiteLoaderListOfficialMain)
     Private Sub DlLiteLoaderListOfficialMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
-        Dim Result As JObject = NetGetCodeByRequestRetry("http://dl.liteloader.com/versions/versions.json", IsJson:=True)
+        Dim Result As JObject = NetGetCodeByRequestRetry("https://dl.liteloader.com/versions/versions.json", IsJson:=True)
         Try
             Dim Json As JObject = Result("versions")
             Dim Versions As New List(Of DlLiteLoaderListEntry)

+ 3 - 0
Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb

@@ -135,6 +135,8 @@
                 If IsJre AndAlso VersionCode >= 16 Then Throw New Exception("由于高版本 JRE 对游戏的兼容性很差,因此不再允许使用。你可以使用对应版本的 JDK,而非 JRE!")
             Catch ex As ApplicationException
                 Throw ex
+            Catch ex As ThreadInterruptedException
+                Throw ex
             Catch ex As Exception
                 Log("[Java] 检查失败的 Java 输出:" & PathFolder & "java.exe" & vbCrLf & If(Output, "无程序输出"))
                 Throw New Exception("检查 Java 失败(" & If(PathJavaw, "Nothing") & ")", ex)
@@ -605,6 +607,7 @@ NoUserJava:
                                                   SyncLock ListLock
                                                       JavaCheckList.Add(Entry)
                                                   End SyncLock
+                                              Catch ex As ThreadInterruptedException
                                               Catch ex As Exception
                                                   If Entry.IsUserImport Then
                                                       Log(ex, "位于 " & Entry.PathFolder & " 的 Java 存在异常,将被自动移除", LogLevel.Hint)

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

@@ -881,7 +881,7 @@ SystemBrowser:
         End If
         Dim Result As String
         Try
-            Result = NetRequestMulty("https://login.live.com/oauth20_token.srf", "POST", Request, "application/x-www-form-urlencoded", 1)
+            Result = NetRequestMulty("https://login.live.com/oauth20_token.srf", "POST", Request, "application/x-www-form-urlencoded", 2)
         Catch ex As Exception
             If ex.Message.Contains("must sign in again") OrElse ex.Message.Contains("invalid_grant") Then '#269
                 Return {"Relogin", ""}
@@ -1000,8 +1000,8 @@ SystemBrowser:
             ElseIf Message.Contains("(404)") Then
                 Log(ex, "微软登录第 6 步汇报 404")
                 RunInNewThread(Sub()
-                                   Select Case MyMsgBox("你可能没有在 Minecraft 官网创建档案,Xbox Game Pass 已到期,或者还没有购买 Minecraft。" & vbCrLf &
-                                            "如果你确认拥有 Minecraft,请在官网上创建档案后再试。", "登录失败", "创建档案", "购买 Minecraft", "取消")
+                                   Select Case MyMsgBox("你可能没有创建 Minecraft 玩家档案、Xbox Game Pass 已到期,或者还没有购买 Minecraft。" & vbCrLf &
+                                            "请先使用官方启动器登录一次,之后即可使用 PCL 登录。", "登录失败", "创建档案", "购买 Minecraft", "取消")
                                        Case 1
                                            OpenWebsite("https://www.minecraft.net/zh-hans/msaprofile/mygames/editprofile")
                                        Case 2
@@ -1210,6 +1210,7 @@ SystemBrowser:
         Loop
 
         '检查下载结果
+        If JavaSearchLoader.State <> LoadState.Loading Then JavaSearchLoader.State = LoadState.Waiting '2872#
         McLaunchJavaSelected = JavaSelect("$$", MinVer, MaxVer, McVersionCurrent)
         If Task.IsAborted Then Exit Sub
         If McLaunchJavaSelected IsNot Nothing Then
@@ -1857,7 +1858,7 @@ NextVersion:
                         End If
                     Case Else '快照版是 99
                         PackFormat = 17
-                        'https://minecraft.fandom.com/zh/wiki/数据包#数据包版本
+                        'https://zh.minecraft.wiki/w/数据包#数据包版本
                 End Select
                 McLaunchLog("正在构建自定义皮肤资源包,格式为:" & PackFormat)
                 '准备文件

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

@@ -1958,9 +1958,7 @@ NextVersion:
             Dim Urls As New List(Of String)
             If Token.Url IsNot Nothing Then
                 '获取 Url 的真实地址
-                Dim Url As String = Token.Url
-                Url = Url.Replace("https://files.minecraftforge.net", "http://files.minecraftforge.net") '这个鬼东西不让 https
-                Urls.Add(Url)
+                Urls.Add(Token.Url)
                 If Token.Url.Contains("launcher.mojang.com/v1/objects") Then Urls = DlSourceLauncherOrMetaGet(Token.Url).ToList() 'Mappings
                 If Token.Url.Contains("maven") Then Urls.Insert(0, Token.Url.Replace(Mid(Token.Url, 1, Token.Url.IndexOf("maven")), "https://download.mcbbs.net/").Replace("maven.fabricmc.net", "maven").Replace("maven.minecraftforge.net", "maven"))
             End If
@@ -1974,8 +1972,8 @@ NextVersion:
                 Dim OptiFineBase As String = Token.LocalPath.Replace(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries\optifine\OptiFine\", "").Split("_")(0) & "/" & GetFileNameFromPath(Token.LocalPath).Replace("-", "_")
                 OptiFineBase = "/maven/com/optifine/" & OptiFineBase
                 If OptiFineBase.Contains("_pre") Then OptiFineBase = OptiFineBase.Replace("com/optifine/", "com/optifine/preview_")
-                Urls.Add("http://download.mcbbs.net" & OptiFineBase)
-                Urls.Add("http://bmclapi2.bangbang93.com" & OptiFineBase)
+                Urls.Add("https://download.mcbbs.net" & OptiFineBase)
+                Urls.Add("https://bmclapi2.bangbang93.com" & OptiFineBase)
             ElseIf Urls.Count <= 2 Then
                 '普通文件
                 Urls.AddRange(DlSourceLibraryGet("https://libraries.minecraft.net" & Token.LocalPath.Replace(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries", "").Replace("\", "/")))
@@ -2188,7 +2186,7 @@ NextVersion:
                 '检查文件是否存在
                 Dim File As New FileInfo(Token.LocalPath)
                 If File.Exists AndAlso (Token.Size = 0 OrElse Token.Size = File.Length) AndAlso
-                    (Not CheckHash OrElse Token.Hash Is Nothing OrElse Token.Hash = GetAuthSHA1(Token.LocalPath)) Then Continue For
+                    (Not CheckHash OrElse Token.Hash Is Nothing OrElse Token.Hash = GetFileSHA1(Token.LocalPath)) Then Continue For
                 '文件不存在,添加下载
                 Result.Add(New NetFile(DlSourceResourceGet("https://resources.download.minecraft.net/" & Left(Token.Hash, 2) & "/" & Token.Hash), Token.LocalPath, New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.Hash)))
             Next

+ 266 - 22
Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb

@@ -274,31 +274,25 @@ Public Module ModMod
             If IsLoaded AndAlso Not ForceReload Then Exit Sub
             '初始化
             Init()
-            '阶段 1:基础可用性检查
-            Dim Jar As ZipArchive
+            Dim Jar As ZipArchive = Nothing
             Try
-                '打开 Jar 文件
+                '阶段 1:基础可用性检查、打开 Jar 文件
                 If Path.Length < 2 Then Throw New FileNotFoundException("错误的 Mod 文件路径(" & If(Path, "null") & ")")
                 If Not File.Exists(Path) Then Throw New FileNotFoundException("未找到 Mod 文件(" & Path & ")")
                 Jar = New ZipArchive(New FileStream(Path, FileMode.Open))
                 If Jar.Entries.Count = 0 Then Throw New FileFormatException("文件内容为空")
+                '阶段 2:信息获取
+                LoadWithoutClass(Jar)
+                '阶段 3: Class 信息获取
+                If Not IsInfoWithoutClassAvailable Then LoadWithClass(Jar)
             Catch ex As UnauthorizedAccessException
                 Log(ex, "Mod 文件由于无权限无法打开(" & Path & ")", LogLevel.Developer)
                 _FileUnavailableReason = New UnauthorizedAccessException("没有读取此文件的权限,请尝试右键以管理员身份运行 PCL", ex)
-                Jar = Nothing
             Catch ex As Exception
                 Log(ex, "Mod 文件无法打开(" & Path & ")", LogLevel.Developer)
                 _FileUnavailableReason = ex
-                Jar = Nothing
-            End Try
-            '阶段 2:信息获取
-            If Jar IsNot Nothing Then LoadWithoutClass(Jar)
-            '阶段 3: Class 信息获取
-            If Jar IsNot Nothing AndAlso Not IsInfoWithoutClassAvailable Then LoadWithClass(Jar)
-            '释放文件
-            Try
+            Finally
                 If Jar IsNot Nothing Then Jar.Dispose()
-            Catch
             End Try
             '完成标记
             IsLoaded = True
@@ -695,6 +689,85 @@ VersionFindFail:
             IsInfoWithClassAvailable = _ModId IsNot Nothing AndAlso _Version IsNot Nothing
         End Sub
 
+#End Region
+
+#Region "网络信息"
+
+        ''' <summary>
+        ''' 该 Mod 关联的网络项目。
+        ''' </summary>
+        Public Property Comp As CompProject
+            Get
+                Return _Comp
+            End Get
+            Set(value As CompProject)
+                _Comp = value
+                RaiseEvent OnGetCompProject(Me)
+            End Set
+        End Property
+        Private _Comp As CompProject
+        Public IsCompFromModrinth = False
+        Public Event OnGetCompProject(sender As McMod)
+
+        ''' <summary>
+        ''' 获取用于 CurseForge 信息获取的 Hash 值(MurmurHash2)。
+        ''' </summary>
+        Public ReadOnly Property CurseForgeHash As UInteger
+            Get
+                If _CurseForgeHash Is Nothing Then
+                    '读取文件
+                    Dim data As New List(Of Byte)
+                    For Each b As Byte In File.ReadAllBytes(Path)
+                        If b = 9 OrElse b = 10 OrElse b = 13 OrElse b = 32 Then Continue For
+                        data.Add(b)
+                    Next
+                    '计算 MurmurHash2
+                    Dim length As Integer = data.Count
+                    Dim h As UInteger = 1 Xor length '1 是种子
+                    Dim i As Integer
+                    For i = 0 To length - 4 Step 4
+                        Dim k As UInteger = data(i) Or CUInt(data(i + 1)) << 8 Or CUInt(data(i + 2)) << 16 Or CUInt(data(i + 3)) << 24
+                        k = (k * &H5BD1E995L) And &HFFFFFFFFL
+                        k = k Xor (k >> 24)
+                        k = (k * &H5BD1E995L) And &HFFFFFFFFL
+                        h = (h * &H5BD1E995L) And &HFFFFFFFFL
+                        h = h Xor k
+                    Next
+                    Select Case length - i
+                        Case 3
+                            h = h Xor (data(i) Or CUInt(data(i + 1)) << 8)
+                            h = h Xor (CUInt(data(i + 2)) << 16)
+                            h = (h * &H5BD1E995L) And &HFFFFFFFFL
+                        Case 2
+                            h = h Xor (data(i) Or CUInt(data(i + 1)) << 8)
+                            h = (h * &H5BD1E995L) And &HFFFFFFFFL
+                        Case 1
+                            h = h Xor data(i)
+                            h = (h * &H5BD1E995L) And &HFFFFFFFFL
+                    End Select
+                    h = h Xor (h >> 13)
+                    h = (h * &H5BD1E995L) And &HFFFFFFFFL
+                    h = h Xor (h >> 15)
+                    _CurseForgeHash = h
+                End If
+                Return _CurseForgeHash
+            End Get
+        End Property
+        Private _CurseForgeHash As UInteger?
+
+        ''' <summary>
+        ''' 获取用于 Modrinth 信息获取的 Hash 值(SHA1)。
+        ''' </summary>
+        Public ReadOnly Property ModrinthHash As String
+            Get
+                If _ModrinthHash Is Nothing Then
+                    _ModrinthHash = GetFileSHA1(Path)
+                End If
+                Return _ModrinthHash
+            End Get
+        End Property
+        Private _ModrinthHash As String
+
 #End Region
 
         ''' <summary>
@@ -741,18 +814,49 @@ VersionFindFail:
             End If
 
             '确定是否显示进度
-            Loader.Progress = 0.03
-            If ModFileList.Count > 100 Then RunInUi(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.ShowProgress = True)
+            Loader.Progress = 0.05
+            If ModFileList.Count > 50 Then RunInUi(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.ShowProgress = True)
+
+            '获取本地文件缓存
+            Dim CachePath As String = PathTemp & "Cache\LocalMod.json"
+            Dim Cache As New JObject
+            Try
+                Dim CacheContent As String = ReadFile(CachePath)
+                If CacheContent <> "" Then
+                    Cache = GetJson(CacheContent)
+                    If Not Cache.ContainsKey("version") OrElse Cache("version").ToObject(Of Integer) <> LocalModCacheVersion Then
+                        Log($"[Mod] 本地 Mod 信息缓存版本已过期,将弃用这些缓存信息")
+                        Cache = New JObject
+                    End If
+                End If
+            Catch ex As Exception
+                Log(ex, "读取本地 Mod 信息缓存失败,已重置")
+            End Try
+            Cache("version") = LocalModCacheVersion
 
-            '加载为 Mod 列表
+            '加载 Mod 列表
             Dim ModList As New List(Of McMod)
+            Dim ModUpdateList As New List(Of McMod)
             For Each ModFile As FileInfo In ModFileList
-                Dim ModEntity As New McMod(ModFile.FullName)
-                ModEntity.Load()
-                ModList.Add(ModEntity)
-                Loader.Progress += 0.9 / ModFileList.Count
+                Loader.Progress += 0.95 / ModFileList.Count
+                If Loader.IsAborted Then Exit Sub
+                '加载 McMod 对象
+                Dim ModEntry As New McMod(ModFile.FullName)
+                ModEntry.Load()
+                ModList.Add(ModEntry)
+                '读取 Comp 缓存
+                If ModEntry.State = McMod.McModState.Unavaliable Then Continue For
+                Dim Hash = ModEntry.ModrinthHash
+                If Cache.ContainsKey(Hash) Then
+                    ModEntry.Comp = New CompProject(Cache(Hash))
+                    ModEntry.IsCompFromModrinth = Not ModEntry.Comp.FromCurseForge
+                    '如果缓存中的信息在 6 小时以内更新过,则无需重新获取
+                    If Date.Now - Cache(Hash)("CacheTime").ToObject(Of Date) < New TimeSpan(6, 0, 0) Then Continue For
+                End If
+                ModUpdateList.Add(ModEntry)
             Next
-            Loader.Progress = 0.93
+            Loader.Progress = 0.99
+            Log($"[Mod] 共有 {ModList.Count} 个 Mod,其中 {ModUpdateList.Where(Function(m) m.Comp Is Nothing).Count} 个需要联网获取信息,{ModUpdateList.Where(Function(m) m.Comp IsNot Nothing).Count} 个需要更新信息")
 
             '排序
             ModList = Sort(ModList, Function(Left As McMod, Right As McMod) As Boolean
@@ -762,18 +866,158 @@ VersionFindFail:
                                             Return Not Right.FileName.CompareTo(Left.FileName)
                                         End If
                                     End Function)
-            Loader.Progress = 0.98
 
             '回设
             If Loader.IsAborted Then Exit Sub
             Loader.Output = ModList
 
+            '开始联网加载
+            If ModUpdateList.Any() Then
+                McModDetailLoader.Start(New KeyValuePair(Of List(Of McMod), JObject)(ModUpdateList, Cache), IsForceRestart:=True)
+            End If
+
+            Loader.Progress = 1
         Catch ex As Exception
             Log(ex, "Mod 列表加载失败", LogLevel.Debug)
             Throw
         End Try
     End Sub
 
+    '联网加载 Mod 详情
+    Private Const LocalModCacheVersion As Integer = 1
+    Public McModDetailLoader As New LoaderTask(Of KeyValuePair(Of List(Of McMod), JObject), Integer)("Mod List Detail Loader", AddressOf McModDetailLoad)
+    Private Sub McModDetailLoad(Loader As LoaderTask(Of KeyValuePair(Of List(Of McMod), JObject), Integer))
+        Dim Mods As List(Of McMod) = Loader.Input.Key
+        Dim Cache As JObject = Loader.Input.Value
+        '获取作为检查目标的加载器和版本
+        Dim TargetMcVersion As McVersionInfo = PageVersionLeft.Version.Version
+        Dim ModLoaders As New List(Of String)
+        If TargetMcVersion.HasForge Then ModLoaders.Add("forge")
+        If TargetMcVersion.HasFabric Then ModLoaders.Add("fabric")
+        If TargetMcVersion.HasLiteLoader Then ModLoaders.Add("liteloader")
+        If Not ModLoaders.Any() Then ModLoaders.AddRange({"forge", "fabric", "liteloader"})
+        Dim McVersions As New List(Of String) From {TargetMcVersion.McName}
+        If TargetMcVersion.McCodeMain > 0 AndAlso TargetMcVersion.McCodeMain < 99 Then
+            McVersions.Add($"1.{TargetMcVersion.McCodeMain}")
+            For i = 1 To TargetMcVersion.McCodeSub
+                McVersions.Add($"1.{TargetMcVersion.McCodeMain}.{i}")
+            Next
+        End If
+        McVersions = McVersions.Distinct().ToList()
+        '开始网络获取
+        Log($"[Mod] 目标加载器:{ModLoaders.Join("/")},版本:{McVersions.Join("/")}")
+        Dim CompletedThread As Integer = 0
+        Dim MainThread As Thread = Thread.CurrentThread
+        '从 Modrinth 获取信息
+        RunInNewThread(
+        Sub()
+            Try
+                '步骤 1:获取 Hash 与对应的工程 ID
+                Dim ModrinthHashes = Mods.Select(Function(m) m.ModrinthHash).ToList()
+                Dim ModrinthUpdate = CType(GetJson(NetRequestRetry("https://api.modrinth.com/v2/version_files/update", "POST",
+                    $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1"", 
+                    ""loaders"": [""{ModLoaders.Join(""",""")}""],""game_versions"": [""{McVersions.Join(""",""")}""]}}", "application/json")), JObject)
+                Log($"[Mod] 从 Modrinth 获取到 {ModrinthUpdate.Count} 个本地 Mod 的对应信息")
+                '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
+                If ModrinthUpdate.Count = 0 Then Exit Sub
+                Dim ModrinthMapping As New Dictionary(Of String, McMod)
+                For Each ModEntity In Mods
+                    If Not ModrinthUpdate.ContainsKey(ModEntity.ModrinthHash) Then Continue For
+                    Dim ProjectId = ModrinthUpdate(ModEntity.ModrinthHash)("project_id").ToString
+                    If CompProjectCache.ContainsKey(ProjectId) Then
+                        ModEntity.Comp = CompProjectCache(ProjectId)
+                    Else
+                        ModrinthMapping.Add(ProjectId, ModEntity)
+                    End If
+                Next
+                If Loader.IsAbortedWithThread(MainThread) Then Exit Sub
+                Log($"[Mod] 读取缓存后还需要从 Modrinth 获取 {ModrinthMapping.Count} 个本地 Mod 的工程信息")
+                '步骤 3:获取工程信息
+                If Not ModrinthMapping.Any() Then Exit Sub
+                Dim ModrinthProject = CType(GetJson(NetRequestRetry(
+                    $"https://api.modrinth.com/v2/projects?ids=[""{ModrinthMapping.Keys.Join(""",""")}""]",
+                    "GET", "", "application/json")), JArray)
+                For Each ProjectJson In ModrinthProject
+                    Dim Project As New CompProject(ProjectJson)
+                    Dim Entry = ModrinthMapping(Project.Id)
+                    If Entry.Comp IsNot Nothing AndAlso Not Entry.IsCompFromModrinth Then
+                        Project.LogoUrl = Entry.Comp.LogoUrl 'Modrinth 部分 Logo 加载不出来
+                    End If
+                    Entry.IsCompFromModrinth = True
+                    Entry.Comp = Project
+                Next
+                Log($"[Mod] 从 Modrinth 获取本地 Mod 信息结束")
+            Catch ex As Exception
+                Log(ex, "从 Modrinth 获取本地 Mod 信息失败")
+            Finally
+                CompletedThread += 1
+            End Try
+        End Sub, "Mod List Detail Loader Modrinth")
+        '从 CurseForge 获取信息
+        RunInNewThread(
+        Sub()
+            Try
+                '步骤 1:获取 Hash 与对应的工程 ID
+                Dim CurseForgeHashes As New List(Of UInteger)
+                For Each ModEntity In Mods
+                    CurseForgeHashes.Add(ModEntity.CurseForgeHash)
+                    If Loader.IsAbortedWithThread(MainThread) Then Exit Sub
+                Next
+                Dim CurseForgeRaw = CType(CType(GetJson(NetRequestRetry("https://api.curseforge.com/v1/fingerprints/432/", "POST",
+                                    $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer)
+                Log($"[Mod] 从 CurseForge 获取到 {CurseForgeRaw.Count} 个本地 Mod 的对应信息")
+                '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
+                If CurseForgeRaw.Count = 0 Then Exit Sub
+                Dim CurseForgeMapping As New Dictionary(Of Integer, McMod)
+                For Each Project In CurseForgeRaw
+                    Dim ProjectId = Project("id").ToString
+                    Dim Hash As UInteger = Project("file")("fileFingerprint")
+                    Dim ModEntity = Mods.Find(Function(m) m.CurseForgeHash = Hash)
+                    If CompProjectCache.ContainsKey(ProjectId) Then
+                        ModEntity.Comp = CompProjectCache(ProjectId)
+                    Else
+                        CurseForgeMapping.Add(ProjectId, ModEntity)
+                    End If
+                Next
+                If Loader.IsAbortedWithThread(MainThread) Then Exit Sub
+                Log($"[Mod] 读取缓存后还需要从 CurseForge 获取 {CurseForgeMapping.Count} 个本地 Mod 的工程信息")
+                '步骤 3:获取工程信息
+                If Not CurseForgeMapping.Any() Then Exit Sub
+                Dim CurseForgeProject = CType(GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods", "POST",
+                                    $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")), JObject)("data")
+                For Each ProjectJson In CurseForgeProject
+                    Dim Project As New CompProject(ProjectJson)
+                    Dim Entry = CurseForgeMapping(Project.Id) '倒查防止 CurseForge 返回的内容有漏
+                    If Entry.Comp IsNot Nothing AndAlso Entry.IsCompFromModrinth Then
+                        Entry.Comp.LogoUrl = Project.LogoUrl 'Modrinth 部分 Logo 加载不出来
+                        Entry.Comp = Entry.Comp '再次触发修改事件
+                        Continue For
+                    End If
+                    Entry.IsCompFromModrinth = False
+                    Entry.Comp = Project
+                Next
+                Log($"[Mod] 从 CurseForge 获取本地 Mod 信息结束")
+            Catch ex As Exception
+                Log(ex, "从 CurseForge 获取本地 Mod 信息失败")
+            Finally
+                CompletedThread += 1
+            End Try
+        End Sub, "Mod List Detail Loader CurseForge")
+        '等待线程结束
+        Do Until CompletedThread = 2
+            If Loader.IsAborted Then Exit Sub
+            Thread.Sleep(10)
+        Loop
+        '保存缓存
+        Mods = Mods.Where(Function(m) m.Comp IsNot Nothing).ToList()
+        Log($"[Mod] 联网获取本地 Mod 信息完成,为 {Mods.Count} 个 Mod 更新缓存")
+        If Not Mods.Any() Then Exit Sub
+        For Each ModEntity In Mods
+            Cache(ModEntity.ModrinthHash) = ModEntity.Comp.ToJson()
+        Next
+        WriteFile(PathTemp & "Cache\LocalMod.json", Cache.ToString(If(ModeDebug, Newtonsoft.Json.Formatting.Indented, Newtonsoft.Json.Formatting.None)))
+    End Sub
+
 #If DEBUG Then
     ''' <summary>
     ''' 检查 Mod 列表中存在的错误,返回错误信息的集合。

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

@@ -29,15 +29,15 @@
     <StackPanel Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="1" VerticalAlignment="Bottom" Margin="0,1,0,0" Orientation="Horizontal" 
                 IsHitTestVisible="False" SnapsToDevicePixels="False" UseLayoutRounding="False">
         <TextBlock x:Name="LabTitle" TextTrimming="WordEllipsis" FontSize="14" Foreground="{DynamicResource ColorBrush1}" />
-        <TextBlock x:Name="LabTitleRaw" TextTrimming="WordEllipsis" FontSize="12" Foreground="{StaticResource ColorBrushGray1}" Opacity="0.4" VerticalAlignment="Bottom" />
+        <TextBlock x:Name="LabTitleRaw" TextTrimming="WordEllipsis" FontSize="12" Foreground="{StaticResource ColorBrushGray1}" Opacity="0.4" VerticalAlignment="Bottom" Visibility="Collapsed" />
     </StackPanel>
     <!-- Tag 与 详情 -->
-    <StackPanel x:Name="PanTags" Grid.Row="2" Grid.Column="3" Orientation="Horizontal" VerticalAlignment="Bottom" Margin="-1,0,0,0">
+    <StackPanel x:Name="PanTags" Grid.Row="2" Grid.Column="3" Orientation="Horizontal" VerticalAlignment="Bottom" Margin="-1,0,1,0" Visibility="Collapsed">
         <!--<Border Background="{DynamicResource ColorBrush6}" Padding="3,1" CornerRadius="3" Margin="0,0,4,0" SnapsToDevicePixels="True" UseLayoutRounding="False">
             <TextBlock Text="科技" Foreground="{DynamicResource ColorBrush2}" FontSize="11" />
         </Border>-->
     </StackPanel>
-    <TextBlock x:Name="LabInfo" Grid.Row="2" Grid.Column="4" VerticalAlignment="Bottom" Margin="1,0,3,0.5"
+    <TextBlock x:Name="LabInfo" Grid.Row="2" Grid.Column="4" VerticalAlignment="Bottom" Margin="0,0,3,0.5"
                TextTrimming="CharacterEllipsis" FontSize="12" Foreground="{StaticResource ColorBrushGray3}"
                IsHitTestVisible="True" SnapsToDevicePixels="False" UseLayoutRounding="False"
                ToolTipService.InitialShowDelay="100" ToolTipService.BetweenShowDelay="100" 

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

@@ -1,8 +1,9 @@
 Public Class MyCompItem
-    Public Uuid As Integer = GetUuid()
 
-#Region "Logo"
+#Region "基础属性"
+    Public Uuid As Integer = GetUuid()
 
+    'Logo
     Private _Logo As String = ""
     Public Property Logo As String
         Get
@@ -44,7 +45,7 @@
             End Try
         End Set
     End Property
-
+    '后台加载 Logo
     Private Sub LogoLoader(Address As String)
         Dim Retry As Boolean = False
         Dim DownloadEnd As String = GetUuid()
@@ -81,6 +82,72 @@ RetryStart:
         End Try
     End Sub
 
+    '标题
+    Public Property Title As String
+        Get
+            Return LabTitle.Text
+        End Get
+        Set(value As String)
+            If LabTitle.Text = value Then Exit Property
+            LabTitle.Text = value
+        End Set
+    End Property
+
+    '副标题
+    Public Property SubTitle As String
+        Get
+            Return If(LabTitleRaw?.Text, "")
+        End Get
+        Set(value As String)
+            If LabTitleRaw.Text = value Then Exit Property
+            LabTitleRaw.Text = value
+            LabTitleRaw.Visibility = If(value = "", Visibility.Collapsed, Visibility.Visible)
+        End Set
+    End Property
+
+    '描述
+    Public Property Description As String
+        Get
+            Return LabInfo.Text
+        End Get
+        Set(value As String)
+            If LabInfo.Text = value Then Exit Property
+            LabInfo.Text = value
+        End Set
+    End Property
+    '指向时扩展描述
+    Private Sub LabInfo_MouseEnter(sender As Object, e As MouseEventArgs) Handles LabInfo.MouseEnter
+        If IsTextTrimmed(LabInfo) Then
+            ToolTipInfo.Content = LabInfo.Text
+            ToolTipInfo.Width = LabInfo.ActualWidth + 25
+            LabInfo.ToolTip = ToolTipInfo
+        Else
+            LabInfo.ToolTip = Nothing
+        End If
+    End Sub
+    Private Function IsTextTrimmed(textBlock As TextBlock) As Boolean
+        Dim typeface As New Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch)
+        Dim formattedText As New FormattedText(textBlock.Text, Thread.CurrentThread.CurrentCulture, textBlock.FlowDirection, typeface, textBlock.FontSize, textBlock.Foreground, DPI)
+        Return formattedText.Width > textBlock.ActualWidth
+    End Function
+
+    'Tag
+    Public WriteOnly Property Tags As List(Of String)
+        Set(value As List(Of String))
+            PanTags.Children.Clear()
+            PanTags.Visibility = If(value.Any(), Visibility.Visible, Visibility.Collapsed)
+            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"" 
+                         SnapsToDevicePixels=""True"" UseLayoutRounding=""False"">
+                   <TextBlock Text=""" & TagText & """ Foreground=""#868686"" FontSize=""11"" />
+                </Border>")
+                PanTags.Children.Add(NewTag)
+            Next
+        End Set
+    End Property
+
 #End Region
 
 #Region "点击"
@@ -104,13 +171,28 @@ RetryStart:
             Log("[Comp] 记录当前已展开的卡片:" & String.Join("、", Titles))
             FrmMain.PageCurrent.Additional(1) = Titles
         End If
-        FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail, .Additional = {sender.Tag, New List(Of String)}})
+        '打开详情页
+        Dim TargetVersion As String, TargetLoader As CompModLoaderType
+        Select Case CType(sender.Tag, CompProject).Type
+            Case CompType.Mod
+                TargetVersion = If(PageDownloadMod.Loader.Input.GameVersion, "")
+            Case CompType.ModPack
+                TargetVersion = If(PageDownloadPack.Loader.Input.GameVersion, "")
+            Case Else 'CompType.ResourcePack
+                'FUTURE: Res
+                TargetVersion = "" 'If(PageDownloadResource.Loader.Input.GameVersion, "")
+        End Select
+        If CType(sender.Tag, CompProject).Type = CompType.Mod AndAlso PageDownloadMod.Loader.Input.ModLoader <> CompModLoaderType.Any Then
+            TargetLoader = PageDownloadMod.Loader.Input.ModLoader
+        End If
+        FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail,
+                           .Additional = {sender.Tag, New List(Of String), TargetVersion, TargetLoader}})
     End Sub
 
     '鼠标点击判定
     Private IsMouseDown As Boolean = False
     Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonDown
-        If IsMouseOver Then IsMouseDown = True
+        If IsMouseOver AndAlso CanInteraction Then IsMouseDown = True
     End Sub
     Private Sub Button_MouseLeave(sender As Object, e As Object) Handles Me.MouseLeave, Me.PreviewMouseLeftButtonUp
         IsMouseDown = False
@@ -151,9 +233,12 @@ RetryStart:
 #End Region
 
     Private StateLast As String
-    Public HasAnimation As Boolean = True
+    ''' <summary>
+    ''' 是否允许交互。目前仅用于 PageDownloadCompDetail 的顶部栏展示:若关闭碰撞检测,则无法展开 Tooltip。
+    ''' </summary>
+    Public CanInteraction As Boolean = True
     Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp
-        If Not HasAnimation Then Exit Sub
+        If Not CanInteraction Then Exit Sub
         '判断当前颜色
         Dim StateNew As String, Time As Integer
         If IsMouseOver Then
@@ -200,20 +285,4 @@ RetryStart:
         End If
     End Sub
 
-    '指向时扩展描述
-    Private Sub LabInfo_MouseEnter(sender As Object, e As MouseEventArgs) Handles LabInfo.MouseEnter
-        If IsTextTrimmed(LabInfo) Then
-            ToolTipInfo.Content = LabInfo.Text
-            ToolTipInfo.Width = LabInfo.ActualWidth + 25
-            LabInfo.ToolTip = ToolTipInfo
-        Else
-            LabInfo.ToolTip = Nothing
-        End If
-    End Sub
-    Private Function IsTextTrimmed(textBlock As TextBlock) As Boolean
-        Dim typeface As New Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch)
-        Dim formattedText As New FormattedText(textBlock.Text, Thread.CurrentThread.CurrentCulture, textBlock.FlowDirection, typeface, textBlock.FontSize, textBlock.Foreground, DPI)
-        Return formattedText.Width > textBlock.ActualWidth
-    End Function
-
 End Class

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

@@ -0,0 +1,43 @@
+<Grid x:Class="MyLocalModItem"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Name="PanBack" Height="44"
+             RenderTransformOrigin="0.5,0.5" Background="{StaticResource ColorBrushSemiTransparent}" SnapsToDevicePixels="True">
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition Width="6" />
+        <ColumnDefinition Width="34" />
+        <ColumnDefinition Width="7" />
+        <ColumnDefinition Width="Auto" />
+        <ColumnDefinition Width="1*" />
+        <ColumnDefinition Width="4" x:Name="ColumnPaddingRight" />
+    </Grid.ColumnDefinitions>
+    <Grid.RowDefinitions>
+        <RowDefinition Height="1*" />
+        <RowDefinition Height="17" />
+        <RowDefinition Height="18" />
+        <RowDefinition Height="1*" />
+    </Grid.RowDefinitions>
+    <!-- Logo -->
+    <Border Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" IsHitTestVisible="False" SnapsToDevicePixels="True" UseLayoutRounding="True" 
+                                HorizontalAlignment="Right" VerticalAlignment="Center" Width="34" Height="34">
+        <Border.Clip>
+            <RectangleGeometry Rect="0,0,34,34" RadiusX="6" RadiusY="6" />
+        </Border.Clip>
+        <Image x:Name="PathLogo" RenderOptions.BitmapScalingMode="HighQuality" />
+    </Border>
+    <!-- 标题 -->
+    <StackPanel Grid.Column="3" Grid.ColumnSpan="2" Grid.Row="1" VerticalAlignment="Bottom" Margin="0,1,0,0" Orientation="Horizontal" 
+                IsHitTestVisible="False" SnapsToDevicePixels="False" UseLayoutRounding="False">
+        <TextBlock x:Name="LabTitle" TextTrimming="WordEllipsis" FontSize="14" Foreground="{Binding Foreground, ElementName=PanBack}" />
+        <TextBlock x:Name="LabTitleRaw" TextTrimming="WordEllipsis" FontSize="12" Foreground="{StaticResource ColorBrushGray1}" Opacity="0.4" VerticalAlignment="Bottom" Visibility="Collapsed" />
+    </StackPanel>
+    <!-- Tag 与 详情 -->
+    <StackPanel x:Name="PanTags" Grid.Row="2" Grid.Column="3" Orientation="Horizontal" VerticalAlignment="Bottom" Margin="-1,0,1,0" Visibility="Collapsed" IsHitTestVisible="False">
+        <!--<Border Background="{DynamicResource ColorBrush6}" Padding="3,1" CornerRadius="3" Margin="0,0,4,0" SnapsToDevicePixels="True" UseLayoutRounding="False">
+            <TextBlock Text="科技" Foreground="{DynamicResource ColorBrush2}" FontSize="11" />
+        </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}"
+               IsHitTestVisible="False" SnapsToDevicePixels="False" UseLayoutRounding="False" />
+</Grid>

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

@@ -0,0 +1,453 @@
+Imports System.Windows.Forms
+
+Public Class MyLocalModItem
+
+#Region "基础属性"
+    Public Uuid As Integer = GetUuid()
+
+    'Logo
+    Private _Logo As String = ""
+    Public Property Logo As String
+        Get
+            Return _Logo
+        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.ToLower.StartsWith("http") Then
+                    '网络图片
+                    If File.Exists(FileAddress) Then
+                        PathLogo.Source = New MyBitmap(FileAddress)
+                    ElseIf _Logo.ToLower.EndsWith(".webp") Then 'Modrinth 林业 Mod 使用了不支持的 WebP 格式 Logo
+                        Log($"[LocalModItem] 发现不支持的 WebP 格式图标,已更改为默认图标:{_Logo}")
+                        PathLogo.Source = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png")
+                    Else
+                        PathLogo.Source = New MyBitmap("pack://application:,,,/images/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
+        End Set
+    End Property
+    '后台加载 Logo
+    Private Sub LogoLoader(Address As String)
+        Dim Retry As Boolean = False
+        Dim DownloadEnd As String = GetUuid()
+RetryStart:
+        Try
+            NetDownload(_Logo, Address & DownloadEnd)
+            Dim LoadError As Exception = Nothing
+            RunInUiWait(Sub()
+                            Try
+                                '在地址更换时取消加载
+                                If Not Address = PathTemp & "CompLogo\" & GetHash(_Logo) & ".png" Then Exit Sub
+                                '在完成正常加载后才保存缓存图片
+                                PathLogo.Source = New MyBitmap(Address & DownloadEnd)
+                            Catch ex As Exception
+                                Log(ex, "读取本地 Mod 图标失败(" & Address & ")")
+                                File.Delete(Address & DownloadEnd)
+                                LoadError = ex
+                            End Try
+                        End Sub)
+            If LoadError IsNot Nothing Then Throw LoadError
+            If File.Exists(Address) Then
+                File.Delete(Address & DownloadEnd)
+            Else
+                FileIO.FileSystem.MoveFile(Address & DownloadEnd, Address)
+            End If
+        Catch ex As Exception
+            If Not Retry Then
+                Retry = True
+                GoTo RetryStart
+            Else
+                Log(ex, $"下载本地 Mod 图标失败({_Logo})")
+                RunInUi(Sub() PathLogo.Source = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png"))
+            End If
+        End Try
+    End Sub
+
+    '标题
+    Private _Title As String
+    Public Property Title As String
+        Get
+            Return _Title
+        End Get
+        Set(value As String)
+            Dim RawValue = value
+            Select Case Entry.State
+                Case McMod.McModState.Disabled
+                    value &= " [禁用]"
+                Case McMod.McModState.Unavaliable
+                    value &= " [错误]"
+            End Select
+            If LabTitle.Text = value Then Exit Property
+            LabTitle.Text = value
+            _Title = RawValue
+        End Set
+    End Property
+
+    '副标题
+    Public Property SubTitle As String
+        Get
+            Return If(LabTitleRaw?.Text, "")
+        End Get
+        Set(value As String)
+            If LabTitleRaw.Text = value Then Exit Property
+            LabTitleRaw.Text = value
+            LabTitleRaw.Visibility = If(value = "", Visibility.Collapsed, Visibility.Visible)
+        End Set
+    End Property
+
+    '描述
+    Public Property Description As String
+        Get
+            Return LabInfo.Text
+        End Get
+        Set(value As String)
+            If LabInfo.Text = value Then Exit Property
+            LabInfo.Text = value
+        End Set
+    End Property
+
+    'Tag
+    Public WriteOnly Property Tags As List(Of String)
+        Set(value As List(Of String))
+            PanTags.Children.Clear()
+            PanTags.Visibility = If(value.Any(), Visibility.Visible, Visibility.Collapsed)
+            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"" 
+                         SnapsToDevicePixels=""True"" UseLayoutRounding=""False"">
+                   <TextBlock Text=""" & TagText & """ Foreground=""#868686"" FontSize=""11"" />
+                </Border>")
+                PanTags.Children.Add(NewTag)
+            Next
+        End Set
+    End Property
+
+    '相关联的 Mod
+    Public Property Entry As McMod
+        Get
+            Return Tag
+        End Get
+        Set(value As McMod)
+            Tag = value
+        End Set
+    End Property
+
+    '前景色绑定
+    Public Property Foreground As Brush
+        Get
+            Return GetValue(ForegroundProperty)
+        End Get
+        Set(value As Brush)
+            SetValue(ForegroundProperty, value)
+        End Set
+    End Property
+    Public Shared ReadOnly ForegroundProperty As DependencyProperty = DependencyProperty.Register("Foreground", GetType(Brush), GetType(MyLocalModItem), New PropertyMetadata(CType(Color1, SolidColorBrush)))
+
+#End Region
+
+#Region "点击与勾选"
+
+    '触发点击事件
+    Public Event Click(sender As Object, e As MouseButtonEventArgs)
+    Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp
+        If IsMouseDown Then
+            RaiseEvent Click(sender, e)
+            If e.Handled Then Exit Sub
+            Log("[Control] 按下本地 Mod 列表项:" & LabTitle.Text)
+        End If
+    End Sub
+
+    '鼠标点击判定
+    Private IsMouseDown As Boolean = False
+    Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonDown
+        If Not IsMouseDirectlyOver Then Exit Sub
+        IsMouseDown = True
+        If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = False
+    End Sub
+    Private Sub Button_MouseLeave(sender As Object, e As Object) Handles Me.MouseLeave, Me.PreviewMouseLeftButtonUp
+        IsMouseDown = False
+        If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = True
+    End Sub
+
+    '勾选状态
+    Public Event Check(sender As Object, e As RouteEventArgs)
+    Public Event Changed(sender As Object, e As RouteEventArgs)
+    Private _Checked As Boolean = False
+    Public Property Checked As Boolean
+        Get
+            Return _Checked
+        End Get
+        Set(value As Boolean)
+            Try
+                '触发属性值修改
+                Dim ChangedEventArgs As New RouteEventArgs(False)
+                Dim RawValue = _Checked
+                If value = _Checked Then Exit Property
+                _Checked = value
+                If IsInitialized Then
+                    RaiseEvent Changed(Me, ChangedEventArgs)
+                    If ChangedEventArgs.Handled Then
+                        _Checked = RawValue
+                        Exit Property
+                    End If
+                End If
+                If value Then
+                    Dim CheckEventArgs As New RouteEventArgs(False)
+                    RaiseEvent Check(Me, CheckEventArgs)
+                    If CheckEventArgs.Handled Then Exit Property
+                End If
+                '更改动画
+                If IsVisibleInForm() Then
+                    Dim Anim As New List(Of AniData)
+                    If Checked Then
+                        '由无变有
+                        Dim Delta = 32 - RectCheck.ActualHeight
+                        Anim.Add(AaHeight(RectCheck, Delta * 0.4, 200,, New AniEaseOutFluent(AniEasePower.Weak)))
+                        Anim.Add(AaHeight(RectCheck, Delta * 0.6, 300,, New AniEaseOutBack(AniEasePower.Weak)))
+                        Anim.Add(AaOpacity(RectCheck, 1 - RectCheck.Opacity, 30))
+                        RectCheck.VerticalAlignment = VerticalAlignment.Center
+                        RectCheck.Margin = New Thickness(-3, 0, 0, 0)
+                        Anim.Add(AaColor(Me, ForegroundProperty, "ColorBrush2", 200))
+                    Else
+                        '由有变无
+                        Anim.Add(AaHeight(RectCheck, -RectCheck.ActualHeight, 120,, New AniEaseInFluent(AniEasePower.Weak)))
+                        Anim.Add(AaOpacity(RectCheck, -RectCheck.Opacity, 70, 40))
+                        RectCheck.VerticalAlignment = VerticalAlignment.Center
+                        Anim.Add(AaColor(Me, ForegroundProperty, "ColorBrush1", 120))
+                    End If
+                    AniStart(Anim, "MyLocalModItem Checked " & Uuid)
+                Else
+                    '不在窗口上时直接设置
+                    RectCheck.VerticalAlignment = VerticalAlignment.Center
+                    RectCheck.Margin = New Thickness(-3, 0, 0, 0)
+                    If Checked Then
+                        RectCheck.Height = 32
+                        RectCheck.Opacity = 1
+                        Foreground = CType(FindResource("ColorBrush2"), SolidColorBrush)
+                    Else
+                        RectCheck.Height = 0
+                        RectCheck.Opacity = 0
+                        Foreground = CType(FindResource("ColorBrush1"), SolidColorBrush)
+                    End If
+                    AniStop("MyLocalModItem Checked " & Uuid)
+                End If
+
+            Catch ex As Exception
+                Log(ex, "设置 Checked 失败")
+            End Try
+        End Set
+    End Property
+    Private Sub ColorInit(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
+        SetResourceReference(ForegroundProperty, "ColorBrush1")
+    End Sub
+
+
+#End Region
+
+#Region "后加载内容"
+
+    '右下角状态指示图标
+    Private ImgState As Image
+
+    '指向背景
+    Private _RectBack As Border = Nothing
+    Public ReadOnly Property RectBack As Border
+        Get
+            If _RectBack Is Nothing Then
+                Dim Rect As New Border With {
+                    .Name = "RectBack",
+                    .CornerRadius = New CornerRadius(3),
+                    .RenderTransform = New ScaleTransform(0.8, 0.8),
+                    .RenderTransformOrigin = New Point(0.5, 0.5),
+                    .BorderThickness = New Thickness(GetWPFSize(1)),
+                    .SnapsToDevicePixels = True,
+                    .IsHitTestVisible = False,
+                    .Opacity = 0
+                }
+                Rect.SetResourceReference(Border.BackgroundProperty, "ColorBrush7")
+                Rect.SetResourceReference(Border.BorderBrushProperty, "ColorBrush6")
+                SetColumnSpan(Rect, 999)
+                SetRowSpan(Rect, 999)
+                Children.Insert(0, Rect)
+                _RectBack = Rect
+                '<!--<Border x:Name = "RectBack" CornerRadius="3" RenderTransformOrigin="0.5,0.5" SnapsToDevicePixels="True" 
+                'IsHitTestVisible = "False" Opacity="0" BorderThickness="1" 
+                'Grid.ColumnSpan = "4" Background="{DynamicResource ColorBrush7}" BorderBrush="{DynamicResource ColorBrush6}"/>-->
+            End If
+            Return _RectBack
+        End Get
+    End Property
+
+    '按钮
+    Public ButtonHandler As Action(Of MyLocalModItem, EventArgs)
+    Public ButtonStack As FrameworkElement
+    Private _Buttons As IEnumerable(Of MyIconButton)
+    Public Property Buttons As IEnumerable(Of MyIconButton)
+        Get
+            Return _Buttons
+        End Get
+        Set(value As IEnumerable(Of MyIconButton))
+            _Buttons = value
+            '移除原 Stack
+            If ButtonStack IsNot Nothing Then
+                Children.Remove(ButtonStack)
+                ButtonStack = Nothing
+            End If
+            If value.Count = 0 Then Exit Property
+            '添加新 Stack
+            ButtonStack = New StackPanel With {.Opacity = 0, .Margin = New Thickness(0, 0, 5, 0), .SnapsToDevicePixels = False, .Orientation = Orientation.Horizontal,
+                .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Center, .UseLayoutRounding = False}
+            SetColumnSpan(ButtonStack, 10) : SetRowSpan(ButtonStack, 10)
+            '构造按钮
+            For Each Btn As MyIconButton In value
+                If Btn.Height.Equals(Double.NaN) Then Btn.Height = 25
+                If Btn.Width.Equals(Double.NaN) Then Btn.Width = 25
+                CType(ButtonStack, StackPanel).Children.Add(Btn)
+            Next
+            Children.Add(ButtonStack)
+        End Set
+    End Property
+
+    '勾选条
+    Private _RectCheck As Border
+    Public ReadOnly Property RectCheck As Border
+        Get
+            If _RectCheck Is Nothing Then
+                _RectCheck = New Border With {.Width = 5, .Height = If(Checked, Double.NaN, 0), .CornerRadius = New CornerRadius(2, 2, 2, 2),
+                    .VerticalAlignment = If(Checked, VerticalAlignment.Stretch, VerticalAlignment.Center),
+                    .HorizontalAlignment = HorizontalAlignment.Left, .UseLayoutRounding = False, .SnapsToDevicePixels = False,
+                    .Margin = If(Checked, New Thickness(-3, 6, 0, 6), New Thickness(-3, 0, 0, 0))}
+                _RectCheck.SetResourceReference(Border.BackgroundProperty, "ColorBrush3")
+                SetRowSpan(_RectCheck, 10)
+                Children.Add(_RectCheck)
+            End If
+            Return _RectCheck
+        End Get
+    End Property
+
+#End Region
+
+    Public Sub Refresh()
+        RunInUi(
+        Sub()
+            '标题
+            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
+            '描述
+            Dim NewDescription As String
+            Select Case Entry.State
+                Case McMod.McModState.Fine
+                    NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path)
+                Case McMod.McModState.Disabled
+                    NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path.Substring(0, Entry.Path.Count - ".disabled".Count))
+                Case Else 'McMod.McModState.Unavaliable
+                    NewDescription = GetFileNameFromPath(Entry.Path)
+            End Select
+            If Entry.Comp IsNot Nothing Then
+                NewDescription += ": " & Entry.Comp.Description.Replace(vbCr, "").Replace(vbLf, "")
+            ElseIf Entry.Description IsNot Nothing Then
+                NewDescription += ": " & Entry.Description.Replace(vbCr, "").Replace(vbLf, "")
+            ElseIf Not Entry.IsFileAvailable Then
+                NewDescription += ": " & "存在错误,无法获取信息"
+            End If
+            Description = NewDescription
+            '主 Logo
+            Logo = If(Entry.Comp Is Nothing, "pack://application:,,,/images/Icons/NoIcon.png", Entry.Comp.GetControlLogo())
+            '图标右下角的 Logo
+            If Entry.State = McMod.McModState.Fine Then
+                If ImgState IsNot Nothing Then
+                    Children.Remove(ImgState)
+                    ImgState = Nothing
+                End If
+            Else
+                If ImgState Is Nothing Then
+                    ImgState = New Image With {
+                                .Width = 17, .Height = 17, .Margin = New Thickness(0, 0, -4, -3), .IsHitTestVisible = False,
+                                .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Bottom
+                            }
+                    RenderOptions.SetBitmapScalingMode(ImgState, BitmapScalingMode.HighQuality)
+                    SetColumn(ImgState, 1) : SetRow(ImgState, 1) : SetRowSpan(ImgState, 2)
+                    Children.Add(ImgState)
+                    '<Image x:Name="ImgState" RenderOptions.BitmapScalingMode="HighQuality" Width="16" Height="16" Margin="0,0,-3,-1"
+                    '       Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" IsHitTestVisible="False"
+                    '       HorizontalAlignment="Right" VerticalAlignment="Bottom"
+                    '       Source="/Images/Icons/Unavaliable.png" />
+                End If
+                ImgState.Source = New MyBitmap($"pack://application:,,,/images/Icons/{Entry.State}.png")
+            End If
+            '标签
+            If Entry.Comp IsNot Nothing Then Tags = Entry.Comp.Tags
+        End Sub)
+    End Sub
+
+    Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp, Me.Changed
+        '按钮虚拟化检测
+        If ButtonHandler IsNot Nothing Then
+            ButtonHandler(sender, e)
+            ButtonHandler = Nothing
+        End If
+        '触发颜色动画
+        Dim Time As Integer = If(IsMouseOver, 120, 180)
+        Dim Ani As New List(Of AniData)
+        'ButtonStack
+        If ButtonStack IsNot Nothing Then
+            If IsMouseOver Then
+                Ani.Add(AaOpacity(ButtonStack, 1 - ButtonStack.Opacity, Time * 0.7, Time * 0.3))
+                Ani.Add(AaDouble(Sub(i) ColumnPaddingRight.Width = New GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + i)),
+                                         5 + Buttons.Count * 25 - ColumnPaddingRight.Width.Value, Time * 0.3, Time * 0.7))
+            Else
+                Ani.Add(AaOpacity(ButtonStack, -ButtonStack.Opacity, Time * 0.4))
+                Ani.Add(AaDouble(Sub(i) ColumnPaddingRight.Width = New GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + i)),
+                                     4 - ColumnPaddingRight.Width.Value, Time * 0.4))
+            End If
+        End If
+        'RectBack
+        If IsMouseOver OrElse Checked Then
+            Ani.AddRange({
+                    AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrushBg1"), Time),
+                    AaOpacity(RectBack, 1 - RectBack.Opacity, Time,, New AniEaseOutFluent)
+                })
+            If IsMouseDown Then
+                Ani.Add(AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time * 1.2,, New AniEaseOutFluent))
+            Else
+                Ani.Add(AaScaleTransform(RectBack, 1 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time * 1.2,, New AniEaseOutFluent))
+            End If
+        Else
+            Ani.AddRange({
+                    AaOpacity(RectBack, -RectBack.Opacity, Time),
+                    AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time,, New AniEaseOutFluent),
+                    AaScaleTransform(RectBack, -0.196, 1,,, True)
+                })
+        End If
+        AniStart(Ani, "LocalModItem Color " & Uuid)
+    End Sub
+
+End Class

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

@@ -46,7 +46,7 @@
     ''' </summary>
     Private Function DequeueNextMusicAddress() As String
         '初始化,确保存在音乐
-        If MusicAllList Is Nothing OrElse MusicAllList.Count = 0 Then MusicListInit(False)
+        If MusicAllList Is Nothing OrElse MusicAllList.Count = 0 OrElse MusicWaitingList.Count = 0 Then MusicListInit(False)
         '出列下一个音乐,如果出列结束则生成新列表
         DequeueNextMusicAddress = MusicWaitingList(0)
         MusicWaitingList.RemoveAt(0)
@@ -267,6 +267,7 @@
             '当前音乐已播放结束,继续下一曲
             If CurrentWave.PlaybackState = NAudio.Wave.PlaybackState.Stopped AndAlso MusicAllList.Count > 0 Then MusicStartPlay(DequeueNextMusicAddress)
         Catch ex As Exception
+            Log("[Music] 播放音乐失败的文件完整路径:" & MusicCurrent)
             If ex.Message.Contains("Got a frame at sample rate") OrElse ex.Message.Contains("does not support changes to") Then
                 Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 不支持播放音频属性在中途发生变化的音乐", HintType.Critical)
             ElseIf Not (MusicCurrent.ToLower.EndsWith(".wav") OrElse MusicCurrent.ToLower.EndsWith(".mp3") OrElse MusicCurrent.ToLower.EndsWith(".flac")) Then
@@ -274,10 +275,11 @@
             Else
                 Log(ex, "播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & ")", LogLevel.Hint)
             End If
-            Log(ex, "播放音乐失败(" & MusicCurrent & ")", LogLevel.Developer)
-            If MusicAllList.Count > 1 Then
-                Thread.Sleep(1000)
-                MusicStartPlay(DequeueNextMusicAddress, IsFirstLoad)
+            Thread.Sleep(1000)
+            If TypeOf ex Is FileNotFoundException Then
+                MusicRefreshPlay(True, IsFirstLoad)
+            ElseIf MusicAllList.Count > 1 Then
+                MusicStartPlay(DequeueNextMusicAddress(), IsFirstLoad)
             End If
         Finally
             If CurrentWave IsNot Nothing Then CurrentWave.Dispose()

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

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

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

@@ -190,7 +190,7 @@ Public Module ModDownloadLib
                 Logo = "pack://application:,,,/images/Blocks/CobbleStone.png"
         End Select
         '建立控件
-        Dim NewItem As New MyListItem With {.Logo = Logo, .SnapsToDevicePixels = True, .Title = Entry("id").ToString, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60}
+        Dim NewItem As New MyListItem With {.Logo = Logo, .SnapsToDevicePixels = True, .Title = Entry("id").ToString, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry}
         If Entry("lore") Is Nothing Then
             NewItem.Info = Entry("releaseTime").Value(Of Date).ToString("yyyy/MM/dd HH:mm")
         Else
@@ -718,7 +718,7 @@ Retry:
     Public Function OptiFineDownloadListItem(Entry As DlOptiFineListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyListItem
         '建立控件
         Dim NewItem As New MyListItem With {
-            .Title = Entry.NameDisplay, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60,
+            .Title = Entry.NameDisplay, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
             .Info = If(Entry.IsPreview, "测试版", "正式版") &
                     If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime) &
                     If(Entry.RequiredForgeVersion Is Nothing, ",不兼容 Forge", If(Entry.RequiredForgeVersion = "", "", ",推荐 Forge 版本:" & Entry.RequiredForgeVersion)),
@@ -844,17 +844,17 @@ Retry:
                 '老版本
                 Select Case DownloadInfo.Inherit
                     Case "1.7.10"
-                        Address.Add("http://dl.liteloader.com/redist/1.7.10/liteloader-installer-1.7.10-04.jar")
+                        Address.Add("https://dl.liteloader.com/redist/1.7.10/liteloader-installer-1.7.10-04.jar")
                     Case "1.7.2"
-                        Address.Add("http://dl.liteloader.com/redist/1.7.2/liteloader-installer-1.7.2-04.jar")
+                        Address.Add("https://dl.liteloader.com/redist/1.7.2/liteloader-installer-1.7.2-04.jar")
                     Case "1.6.4"
-                        Address.Add("http://dl.liteloader.com/redist/1.6.4/liteloader-installer-1.6.4-01.jar")
+                        Address.Add("https://dl.liteloader.com/redist/1.6.4/liteloader-installer-1.6.4-01.jar")
                     Case "1.6.2"
-                        Address.Add("http://dl.liteloader.com/redist/1.6.2/liteloader-installer-1.6.2-04.jar")
+                        Address.Add("https://dl.liteloader.com/redist/1.6.2/liteloader-installer-1.6.2-04.jar")
                     Case "1.5.2"
                         'Address.AddRange({"https://download.mcbbs.net/maven/com/mumfrey/liteloader/1.5.2/liteloader-1.5.2.jar",
                         '                  "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/1.5.2/liteloader-1.5.2.jar"})
-                        Address.Add("http://dl.liteloader.com/redist/1.5.2/liteloader-installer-1.5.2-01.jar")
+                        Address.Add("https://dl.liteloader.com/redist/1.5.2/liteloader-installer-1.5.2-01.jar")
                     Case Else
                         Throw New NotSupportedException("未知的 Minecraft 版本(" & DownloadInfo.Inherit & ")")
                 End Select
@@ -921,7 +921,7 @@ Retry:
                                                                     VersionJson.Add("type", "release")
                                                                     VersionJson.Add("arguments", GetJson("{""game"":[""--tweakClass"",""" & DownloadInfo.JsonToken("tweakClass").ToString & """]}"))
                                                                     VersionJson.Add("libraries", DownloadInfo.JsonToken("libraries"))
-                                                                    CType(VersionJson("libraries"), JContainer).Add(GetJson("{""name"": ""com.mumfrey:liteloader:" & DownloadInfo.JsonToken("version").ToString & """,""url"": ""http://dl.liteloader.com/versions/""}"))
+                                                                    CType(VersionJson("libraries"), JContainer).Add(GetJson("{""name"": ""com.mumfrey:liteloader:" & DownloadInfo.JsonToken("version").ToString & """,""url"": ""https://dl.liteloader.com/versions/""}"))
                                                                     VersionJson.Add("mainClass", "net.minecraft.launchwrapper.Launch")
                                                                     VersionJson.Add("minimumLauncherVersion", 18)
                                                                     VersionJson.Add("inheritsFrom", DownloadInfo.Inherit)
@@ -949,7 +949,7 @@ Retry:
     Public Function LiteLoaderDownloadListItem(Entry As DlLiteLoaderListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyListItem
         '建立控件
         Dim NewItem As New MyListItem With {
-            .Title = Entry.Inherit, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 30,
+            .Title = Entry.Inherit, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
             .Info = If(Entry.IsPreview, "测试版", "稳定版") & If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime),
             .Logo = "pack://application:,,,/images/Blocks/Egg.png"
         }
@@ -965,10 +965,8 @@ Retry:
     End Function
     Private Sub LiteLoaderSaveContMenuBuild(sender As MyListItem, e As EventArgs)
         If sender.Tag.IsLegacy Then
-            sender.PaddingRight = 0
             sender.Buttons = {}
         Else
-            sender.PaddingRight = 30
             Dim BtnList As New MyIconButton With {.Logo = Logo.IconButtonList, .ToolTip = "查看全部版本", .Tag = sender}
             ToolTipService.SetPlacement(BtnList, Primitives.PlacementMode.Center)
             ToolTipService.SetVerticalOffset(BtnList, 30)
@@ -984,10 +982,8 @@ Retry:
         ToolTipService.SetHorizontalOffset(BtnSave, 2)
         AddHandler BtnSave.Click, AddressOf LiteLoaderSave_Click
         If sender.Tag.IsLegacy Then
-            sender.PaddingRight = 30
             sender.Buttons = {BtnSave}
         Else
-            sender.PaddingRight = 60
             Dim BtnList As New MyIconButton With {.Logo = Logo.IconButtonList, .ToolTip = "查看全部版本", .Tag = sender}
             ToolTipService.SetPlacement(BtnList, Primitives.PlacementMode.Center)
             ToolTipService.SetVerticalOffset(BtnList, 30)
@@ -1003,7 +999,7 @@ Retry:
         Else
             Version = sender.Tag.Tag
         End If
-        OpenWebsite("http://jenkins.liteloader.com/view/" & Version.Inherit)
+        OpenWebsite("https://jenkins.liteloader.com/view/" & Version.Inherit)
     End Sub
     Public Sub LiteLoaderSave_Click(sender As Object, e As RoutedEventArgs)
         'ListItem 与小按钮都会调用这个方法
@@ -1107,7 +1103,7 @@ Retry:
                                                                 Files.Add(New NetFile({
                                                                         "https://download.mcbbs.net/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName,
                                                                         "https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName,
-                                                                        "http://files.minecraftforge.net/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName
+                                                                        "https://files.minecraftforge.net/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName
                                                                     }, Target, New FileChecker(MinSize:=64 * 1024, Hash:=DownloadInfo.Hash)))
                                                                 Task.Output = Files
                                                             End Sub) With {.ProgressWeight = 0.1, .Show = False})
@@ -1326,7 +1322,7 @@ Retry:
                 '添加主文件
                 Dim Files As New List(Of NetFile) From {New NetFile({
                     "https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" & Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName,
-                    "http://files.minecraftforge.net/maven/net/minecraftforge/forge/" & Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName,
+                    "https://files.minecraftforge.net/maven/net/minecraftforge/forge/" & Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName,
                     "https://download.mcbbs.net/maven/net/minecraftforge/forge/" & Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName
                 }, InstallerAddress, New FileChecker(MinSize:=64 * 1024, Hash:=DownloadInfo.Hash))}
                 Task.Output = Files
@@ -1580,7 +1576,7 @@ Retry:
     Public Function ForgeDownloadListItem(Entry As DlForgeVersionEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyListItem
         '建立控件
         Dim NewItem As New MyListItem With {
-            .Title = Entry.Version, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60,
+            .Title = Entry.Version, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
             .Info = If(Entry.ReleaseTime = "",
                 If(ModeDebug, "种类:" & Entry.Category & If(Entry.Branch Is Nothing, "", ",开发分支:" & Entry.Branch), ""),
                 "发布于 " & Entry.ReleaseTime & If(ModeDebug, ",种类:" & Entry.Category & If(Entry.Branch Is Nothing, "", ",开发分支:" & Entry.Branch), "")),
@@ -1626,7 +1622,7 @@ Retry:
         Else
             Version = sender.Parent.Parent.Tag
         End If
-        OpenWebsite("http://files.minecraftforge.net/maven/net/minecraftforge/forge/" & Version.Inherit & "-" & Version.Version & "/forge-" & Version.Inherit & "-" & Version.Version & "-changelog.txt")
+        OpenWebsite("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" & Version.Inherit & "-" & Version.Version & "/forge-" & Version.Inherit & "-" & Version.Version & "-changelog.txt")
     End Sub
     Public Sub ForgeSave_Click(sender As Object, e As RoutedEventArgs)
         Dim Version As DlForgeVersionEntry
@@ -1787,7 +1783,7 @@ Retry:
     Public Function FabricDownloadListItem(Entry As JObject, OnClick As MyListItem.ClickEventHandler) As MyListItem
         '建立控件
         Dim NewItem As New MyListItem With {
-            .Title = Entry("version").ToString.Replace("+build", ""), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60,
+            .Title = Entry("version").ToString.Replace("+build", ""), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
             .Info = If(Entry("stable").ToObject(Of Boolean), "稳定版", "测试版"),
             .Logo = "pack://application:,,,/images/Blocks/Fabric.png"
         }
@@ -1798,7 +1794,7 @@ Retry:
     Public Function FabricApiDownloadListItem(Entry As CompFile, OnClick As MyListItem.ClickEventHandler) As MyListItem
         '建立控件
         Dim NewItem As New MyListItem With {
-            .Title = Entry.DisplayName.Split("]")(1).Replace("Fabric API ", "").Replace(" build ", ".").Split("+").First.Trim, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60,
+            .Title = Entry.DisplayName.Split("]")(1).Replace("Fabric API ", "").Replace(" build ", ".").Split("+").First.Trim, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
             .Info = Entry.StatusDescription & ",发布于 " & Entry.ReleaseDate.ToString("yyyy/MM/dd HH:mm"),
             .Logo = "pack://application:,,,/images/Blocks/Fabric.png"
         }
@@ -1809,7 +1805,7 @@ Retry:
     Public Function OptiFabricDownloadListItem(Entry As CompFile, OnClick As MyListItem.ClickEventHandler) As MyListItem
         '建立控件
         Dim NewItem As New MyListItem With {
-            .Title = Entry.DisplayName.ToLower.Replace("optifabric-", "").Replace(".jar", "").Trim.TrimStart("v"), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60,
+            .Title = Entry.DisplayName.ToLower.Replace("optifabric-", "").Replace(".jar", "").Trim.TrimStart("v"), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
             .Info = Entry.StatusDescription & ",发布于 " & Entry.ReleaseDate.ToString("yyyy/MM/dd HH:mm"),
             .Logo = "pack://application:,,,/images/Blocks/OptiFabric.png"
         }

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

@@ -1,6 +1,5 @@
 Public Class PageDownloadCompDetail
     Private CompItem As MyCompItem = Nothing
-    Private Project As CompProject
 
 #Region "加载器"
 
@@ -10,8 +9,12 @@
     '初始化加载器信息
     Private Sub PageDownloadCompDetail_Inited(sender As Object, e As EventArgs) Handles Me.Initialized
         Project = FrmMain.PageCurrent.Additional(0)
+        TargetVersion = FrmMain.PageCurrent.Additional(2)
+        TargetLoader = FrmMain.PageCurrent.Additional(3)
         PageLoaderInit(Load, PanLoad, PanMain, CardIntro, CompFileLoader, AddressOf Load_OnFinish)
     End Sub
+    Private Project As CompProject
+    Private TargetVersion As String, TargetLoader As CompModLoaderType
     '自动重试
     Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged
         Select Case CompFileLoader.State
@@ -39,23 +42,8 @@
         End Sub
     End Class
     Private Sub Load_OnFinish()
-        '获取目标版本与 Mod 加载器
-        Dim TargetVersion As String
-        Select Case Project.Type
-            Case CompType.Mod
-                TargetVersion = If(PageDownloadMod.Loader.Input.GameVersion, "")
-            Case CompType.ModPack
-                TargetVersion = If(PageDownloadPack.Loader.Input.GameVersion, "")
-            Case Else 'CompType.ResourcePack
-                'FUTURE: Res
-                TargetVersion = "" 'If(PageDownloadResource.Loader.Input.GameVersion, "")
-        End Select
-        Dim TargetLoader = CompModLoaderType.Any
-        If Project.Type = CompType.Mod AndAlso PageDownloadMod.Loader.Input.ModLoader <> CompModLoaderType.Any Then
-            TargetLoader = PageDownloadMod.Loader.Input.ModLoader
-        End If
         Dim TargetCardName As String = If(TargetVersion <> "" OrElse TargetLoader <> CompModLoaderType.Any,
-            $"目标版本:{TargetVersion} {If(TargetLoader <> CompModLoaderType.Any, TargetLoader, "")}", "")
+            $"所选版本:{TargetVersion} {If(TargetLoader <> CompModLoaderType.Any, TargetLoader, "")}", "")
         '初始化字典
         Dim Dict As New SortedDictionary(Of String, List(Of CompFile))(New VersionSorterWithSelect(TargetCardName))
         Dict.Add("未知版本", New List(Of CompFile))
@@ -144,8 +132,7 @@
         '放置当前工程
         If CompItem IsNot Nothing Then PanIntro.Children.Remove(CompItem)
         CompItem = Project.ToCompItem(True, True)
-        CompItem.IsHitTestVisible = False
-        CompItem.HasAnimation = False
+        CompItem.CanInteraction = False
         CompItem.Margin = New Thickness(-7, -7, 0, 8)
         PanIntro.Children.Insert(0, CompItem)
 

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

@@ -26,7 +26,7 @@
                                     <ColumnDefinition Width="1*" />
                                     <ColumnDefinition Width="Auto" />
                                 </Grid.ColumnDefinitions>
-                                <local:MyListItem Grid.Column="0" x:Name="ItemSelect" IsHitTestVisible="False" Margin="-7,0,0,0" Height="42" PaddingRight="15" SnapsToDevicePixels="True" Type="None" />
+                                <local:MyListItem Grid.Column="0" x:Name="ItemSelect" IsHitTestVisible="False" Margin="-7,0,0,0" Height="42" SnapsToDevicePixels="True" Type="None" />
                                 <local:MyButton Grid.Column="1" HorizontalAlignment="Right" ColorType="Highlight" Text="开始安装" Margin="0,2,0,0" x:Name="BtnSelectStart" MinWidth="140" Padding="13,0" Height="35" VerticalAlignment="Center" />
                             </Grid>
                             <Grid Margin="0,0,0,5">

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


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

@@ -99,11 +99,13 @@
             Case FormMain.PageSubType.DownloadMod
                 PageDownloadMod.Storage = New CompProjectStorage
                 PageDownloadMod.Page = 0
+                CompProjectCache.Clear()
                 If FrmDownloadMod IsNot Nothing Then FrmDownloadMod.PageLoaderRestart()
                 ItemMod.Checked = True
             Case FormMain.PageSubType.DownloadPack
                 PageDownloadPack.Storage = New CompProjectStorage
                 PageDownloadPack.Page = 0
+                CompProjectCache.Clear()
                 If FrmDownloadPack IsNot Nothing Then FrmDownloadPack.PageLoaderRestart()
                 ItemPack.Checked = True
             Case FormMain.PageSubType.DownloadClient

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

@@ -33,6 +33,7 @@
                         <TextBlock VerticalAlignment="Center" Grid.Row="2" HorizontalAlignment="Left" Text="版本" Margin="0,0,18,0" />
                         <local:MyComboBox x:Name="TextSearchVersion" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" IsEditable="True" MaxDropDownHeight="320">
                             <local:MyComboBoxItem Content="全部 (也可自行输入)" IsSelected="True" />
+                            <local:MyComboBoxItem Content="1.20.3" />
                             <local:MyComboBoxItem Content="1.20.2" />
                             <local:MyComboBoxItem Content="1.20.1" />
                             <local:MyComboBoxItem Content="1.20" />
@@ -97,6 +98,7 @@
                 </StackPanel>
             </local:MyCard>
             <StackPanel x:Name="PanContent">
+                <local:MyHint x:Name="HintError" Text="无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,结果可能不全。&#xa;请尝试使用 VPN 或加速器以改善网络。" IsWarn="True" Margin="0,0,0,15" />
                 <local:MyCard x:Name="CardProjects" UseAnimation="False">
                     <StackPanel x:Name="PanProjects" Margin="12" />
                 </local:MyCard>

+ 7 - 0
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb

@@ -46,6 +46,13 @@
             BtnPageRight.IsEnabled = Storage.Results.Count > PageSize * (Page + 1) OrElse
                                      Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal
             BtnPageRight.Opacity = If(BtnPageRight.IsEnabled, 1, 0.2)
+            '错误信息
+            If Storage.ErrorMessage Is Nothing Then
+                HintError.Visibility = Visibility.Collapsed
+            Else
+                HintError.Visibility = Visibility.Visible
+                HintError.Text = Storage.ErrorMessage
+            End If
             '强制返回顶部
             PanBack.ScrollToTop()
         Catch ex As Exception

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

@@ -32,6 +32,7 @@
                         <TextBlock VerticalAlignment="Center" Grid.Row="2" HorizontalAlignment="Left" Text="版本" Margin="0,0,18,0" />
                         <local:MyComboBox x:Name="TextSearchVersion" Grid.Row="2" Grid.Column="1" IsEditable="True" MaxDropDownHeight="320">
                             <local:MyComboBoxItem Content="全部 (也可自行输入)" IsSelected="True" />
+                            <local:MyComboBoxItem Content="1.20.3" />
                             <local:MyComboBoxItem Content="1.20.2" />
                             <local:MyComboBoxItem Content="1.20.1" />
                             <local:MyComboBoxItem Content="1.20" />
@@ -87,6 +88,7 @@
                 </StackPanel>
             </local:MyCard>
             <StackPanel x:Name="PanContent">
+                <local:MyHint x:Name="HintError" Text="无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,结果可能不全。&#xa;请尝试使用 VPN 或加速器以改善网络。" IsWarn="True" Margin="0,0,0,15" />
                 <local:MyCard x:Name="CardProjects" UseAnimation="False">
                     <StackPanel x:Name="PanProjects" Margin="12" />
                 </local:MyCard>

+ 7 - 0
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml.vb

@@ -45,6 +45,13 @@
             BtnPageRight.IsEnabled = Storage.Results.Count > PageSize * (Page + 1) OrElse
                                      Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal
             BtnPageRight.Opacity = If(BtnPageRight.IsEnabled, 1, 0.2)
+            '错误信息
+            If Storage.ErrorMessage Is Nothing Then
+                HintError.Visibility = Visibility.Collapsed
+            Else
+                HintError.Visibility = Visibility.Visible
+                HintError.Text = Storage.ErrorMessage
+            End If
             '强制返回顶部
             PanBack.ScrollToTop()
         Catch ex As Exception

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

@@ -236,7 +236,8 @@ Retry:
                                                        {"Migrator", "迁移者披风"}, {"MapMaker", "Realms 地图制作者披风"}, {"Moderator", "Mojira 管理员披风"},
                                                        {"Translator-Chinese", "Crowdin 中文翻译者披风"}, {"Translator", "Crowdin 翻译者披风"}, {"Cobalt", "Cobalt 披风"},
                                                        {"Vanilla", "原版披风"}, {"Minecon2011", "Minecon 2011 参与者披风"}, {"Minecon2012", "Minecon 2012 参与者披风"},
-                                                       {"Minecon2013", "Minecon 2013 参与者披风"}, {"Minecon2015", "Minecon 2015 参与者披风"}, {"Minecon2016", "Minecon 2016 参与者披风"}
+                                                       {"Minecon2013", "Minecon 2013 参与者披风"}, {"Minecon2015", "Minecon 2015 参与者披风"}, {"Minecon2016", "Minecon 2016 参与者披风"},
+                                                       {"Cherry Blossom Cape", "樱花披风"}, {"CherryBlossomCape", "樱花披风"}
                                                    }
                                                    Dim SelectionControl As New List(Of IMyRadio) From {New MyRadioBox With {.Text = "无披风"}}
                                                    For Each Cape In SkinData("capes")

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

@@ -92,6 +92,10 @@ Download:
                         Log("[Page] 主页预设:简单主页")
                         Url = "https://cdn.wearclub.online/PCL/Custom.xaml"
                         GoTo Download
+                    Case 4 '历史上的今天
+                        Log("[Page] 主页预设:历史上的今天")
+                        Url = "https://pclnews.52chye.cn/custom.xaml"
+                        GoTo Download
                 End Select
         End Select
         RunInUi(Sub() LoadContent(Content))

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

@@ -14,23 +14,23 @@
                            Margin="0,0,0,6" />
                     <local:MyListItem Title="速聚官网" Info="打开速聚的官方网站"
                               Type="Clickable" EventType="打开网页" EventData="https://www.the.bb/zh-CN.html"
-                              PaddingRight="20" Logo="pack://application:,,,/images/Blocks/Grass.png" />
+                              Logo="pack://application:,,,/images/Blocks/Grass.png" />
                     <local:MyListItem Title="授权协议书" Info="查看速聚与 PCL 间的授权协议书"
                               Type="Clickable" EventType="打开网页" EventData="https://shimo.im/docs/0l3NVXYzeXhb903R"
-                              PaddingRight="20" Logo="pack://application:,,,/images/Blocks/Grass.png" />
+                              Logo="pack://application:,,,/images/Blocks/Grass.png" />
                     <local:MyListItem Title="用户协议与隐私政策" Info="查看速聚的用户协议与隐私政策文档"
                               Type="Clickable" EventType="打开网页" EventData="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzkxMTMyODk3Mg==&amp;action=getalbum&amp;album_id=2585385685407514625&amp;scene=173&amp;from_msgid=2247483720&amp;from_itemidx=1&amp;count=3&amp;nolastread=1#wechat_redirect"
-                              PaddingRight="20" Logo="pack://application:,,,/images/Blocks/Grass.png" />
+                              Logo="pack://application:,,,/images/Blocks/Grass.png" />
                 </StackPanel>
             </local:MyCard>
             <local:MyCard Title="发送反馈" Margin="0,0,0,15">
                 <StackPanel Margin="20,40,18,18">
                     <local:MyListItem Title="联机反馈与交流" Info="加入联机模块官方 QQ 群反馈联机异常,或询问联机功能的使用方式"
-                              Type="Clickable" PaddingRight="20" Logo="pack://application:,,,/images/Blocks/RedstoneBlock.png"
+                              Type="Clickable" Logo="pack://application:,,,/images/Blocks/RedstoneBlock.png"
                               EventType="打开网页" EventData="https://shimo.im/docs/KrkEVXz1K0frowAJ#anchor-7qQG" />
                     <local:MyListItem Title="违法违规举报" Info="投诉、举报联机使用者的各类违法违规行为"
                               Type="Clickable" EventType="打开网页" EventData="https://shimo.im/docs/rGrd8pY8xWkt6ryW#anchor-7x6P"
-                              PaddingRight="20" Logo="pack://application:,,,/images/Blocks/RedstoneBlock.png" />
+                              Logo="pack://application:,,,/images/Blocks/RedstoneBlock.png" />
                 </StackPanel>
             </local:MyCard>
         </StackPanel>

+ 3 - 3
Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb

@@ -197,7 +197,7 @@ Reopen:
                 GoTo FinishHiperFileCheck
             End If
             Log("[Hiper] hiper.exe 的所需 SHA1:" & ChecksumHiper)
-            If ChecksumHiper = GetAuthSHA1(PathHiper & "HiPer 联机模块.exe") Then
+            If ChecksumHiper = GetFileSHA1(PathHiper & "HiPer 联机模块.exe") Then
                 Log("[Hiper] hiper.exe 文件校验通过,无需重新下载")
                 GoTo FinishHiperFileCheck
             End If
@@ -233,7 +233,7 @@ FinishHiperFileCheck:
                 GoTo FinishWintunFileCheck
             End If
             Log("[Hiper] wintun.dll 的所需 SHA1:" & ChecksumWintun)
-            If ChecksumWintun = GetAuthSHA1(PathHiper & "wintun.dll") Then
+            If ChecksumWintun = GetFileSHA1(PathHiper & "wintun.dll") Then
                 Log("[Hiper] wintun.dll 文件校验通过,无需重新下载")
                 GoTo FinishWintunFileCheck
             End If
@@ -269,7 +269,7 @@ FinishWintunFileCheck:
                     GoTo FinishMcbFileCheck
                 End If
                 Log("[Hiper] mcb.exe 的所需 SHA1:" & ChecksumMcb)
-                If ChecksumMcb = GetAuthSHA1(PathHiper & "MCB 联机模块.exe") Then
+                If ChecksumMcb = GetFileSHA1(PathHiper & "MCB 联机模块.exe") Then
                     Log("[Hiper] mcb.exe 文件校验通过,无需重新下载")
                     GoTo FinishMcbFileCheck
                 End If

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

@@ -851,7 +851,6 @@ Done:
         Public Function ToListItem() As MyListItem
             Dim Item As New MyListItem With {
                 .Title = DisplayName, .Height = 42, .Tag = Me, .Type = MyListItem.CheckType.None,
-                .PaddingRight = 60,
                 .Logo = "pack://application:,,,/images/Blocks/Grass.png"}
             '绑定图标按钮
             Dim BtnRefresh As New MyIconButton With {.Logo = Logo.IconButtonRefresh, .LogoScale = 0.85, .ToolTip = "刷新", .Tag = Me}
@@ -941,7 +940,7 @@ Done:
         End Function
         Public Function ToListItem() As MyListItem
             Dim Item As New MyListItem With {
-                .Title = DisplayName, .Height = 42, .Info = GetDescription(), .Tag = Me, .PaddingRight = If(IsOwner, 60, 0),
+                .Title = DisplayName, .Height = 42, .Info = GetDescription(), .Tag = Me,
                 .Type = If(IsOwner, MyListItem.CheckType.None, MyListItem.CheckType.Clickable),
                 .Logo = "pack://application:,,,/images/Blocks/" & If(IsOwner, "GrassPath", "Grass") & ".png"}
             If IsOwner Then

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

@@ -7,7 +7,7 @@
       mc:Ignorable="d" AnimatedControl="{Binding ElementName=PanItem}" MinWidth="150">
     <StackPanel Orientation="Vertical" x:Name="PanItem" Margin="0,12,0,0">
         <TextBlock Text="联机方式" Margin="13,6,5,4" Opacity="0.6" FontSize="12" />
-        <local:MyListItem x:Name="ItemHiper" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="1" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="HiPer"
+        <local:MyListItem x:Name="ItemHiper" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="1" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="HiPer"
                           LogoScale="0.85" Logo="M554.496 170.496c141.312 0 256 114.688 256 256s-114.688 256-256 256H402.432c-22.016-1.536-39.424-19.968-39.424-42.496a42.391 42.391 0 0 1 42.496-42.496h149.504c94.208 0 170.496-76.288 170.496-170.496S648.704 256 554.496 256h-256C204.288 256 128 332.288 128 426.496c0 38.4 12.8 73.728 33.792 102.4 5.632 7.168 8.704 15.872 8.704 25.6A42.391 42.391 0 0 1 128 596.992c-11.264 0-20.992-4.096-28.672-11.264l-0.512 0.512c-35.328-44.032-56.32-99.328-56.32-159.744 0-141.312 114.688-256 256-256h256z m64 192h3.072c22.016 1.536 39.424 19.968 39.424 42.496a42.391 42.391 0 0 1-42.496 42.496H469.504c-94.208 0-170.496 76.288-170.496 170.496S375.296 788.48 469.504 788.48h256C819.712 788.48 896 712.192 896 617.984c0-38.4-12.8-73.728-33.792-102.4-5.632-7.168-8.704-15.872-8.704-25.6A42.391 42.391 0 0 1 896 447.488c11.264 0 20.992 4.096 28.672 11.264l0.512-0.512c35.328 44.032 56.32 99.328 56.32 159.744 0 141.312-114.688 256-256 256h-256c-141.312 0-256-114.688-256-256s114.688-256 256-256h148.992z">
             <local:MyListItem.Buttons>
                 <x:Array Type="{x:Type local:MyIconButton}">
@@ -17,7 +17,7 @@
                 </x:Array>
             </local:MyListItem.Buttons>
         </local:MyListItem>
-        <local:MyListItem x:Name="ItemIoi" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="2" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="IOI"
+        <local:MyListItem x:Name="ItemIoi" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="2" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="IOI"
                           LogoScale="0.85" Logo="M554.496 170.496c141.312 0 256 114.688 256 256s-114.688 256-256 256H402.432c-22.016-1.536-39.424-19.968-39.424-42.496a42.391 42.391 0 0 1 42.496-42.496h149.504c94.208 0 170.496-76.288 170.496-170.496S648.704 256 554.496 256h-256C204.288 256 128 332.288 128 426.496c0 38.4 12.8 73.728 33.792 102.4 5.632 7.168 8.704 15.872 8.704 25.6A42.391 42.391 0 0 1 128 596.992c-11.264 0-20.992-4.096-28.672-11.264l-0.512 0.512c-35.328-44.032-56.32-99.328-56.32-159.744 0-141.312 114.688-256 256-256h256z m64 192h3.072c22.016 1.536 39.424 19.968 39.424 42.496a42.391 42.391 0 0 1-42.496 42.496H469.504c-94.208 0-170.496 76.288-170.496 170.496S375.296 788.48 469.504 788.48h256C819.712 788.48 896 712.192 896 617.984c0-38.4-12.8-73.728-33.792-102.4-5.632-7.168-8.704-15.872-8.704-25.6A42.391 42.391 0 0 1 896 447.488c11.264 0 20.992 4.096 28.672 11.264l0.512-0.512c35.328 44.032 56.32 99.328 56.32 159.744 0 141.312-114.688 256-256 256h-256c-141.312 0-256-114.688-256-256s114.688-256 256-256h148.992z">
             <local:MyListItem.Buttons>
                 <x:Array Type="{x:Type local:MyIconButton}">
@@ -28,17 +28,17 @@
             </local:MyListItem.Buttons>
         </local:MyListItem>
         <TextBlock Text="更多选项" Margin="13,18,5,4" Opacity="0.6" FontSize="12" />
-        <local:MyListItem x:Name="ItemSetup" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="4" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="设置"
+        <local:MyListItem x:Name="ItemSetup" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="4" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="设置"
                           LogoScale="0.85" Logo="M940.4 463.7L773.3 174.2c-17.3-30-49.2-48.4-83.8-48.4H340.2c-34.6 0-66.5 18.5-83.8 48.4L89.2 463.7c-17.3 30-17.3 66.9 0 96.8L256.4 850c17.3 30 49.2 48.4 83.8 48.4h349.2c34.6 0 66.5-18.5 83.8-48.4l167.2-289.5c17.3-29.9 17.3-66.8 0-96.8z m-94.6 96.8L725.9 768.1c-17.3 30-49.2 48.4-83.8 48.4H387.5c-34.6 0-66.5-18.5-83.8-48.4L183.9 560.5c-17.3-30-17.3-66.9 0-96.8l119.8-207.5c17.3-30 49.2-48.4 83.8-48.4h254.6c34.6 0 66.5 18.5 83.8 48.4l119.8 207.5c17.3 30 17.3 66.9 0.1 96.8z M522.3 321.2c-2.5-0.1-5-0.2-7.5-0.2-119.9 0-214 110.3-186.3 235 15.8 70.9 71.5 126.6 142.4 142.4 17.5 3.9 34.7 5.4 51.4 4.7 102.1-3.9 183.6-87.9 183.6-191 0.1-103-81.5-187-183.6-190.9z m68.6 269.1c-18.5 18-43 28.9-68.6 30.7l-6 0.3c-30.2 0.4-58.6-11.4-79.7-33-19.5-20.1-30.7-47-30.9-75-0.3-29.6 11.1-57.4 32-78.3 20.6-20.6 48-32 77.2-32 2.5 0 5 0.1 7.5 0.3 26.7 1.8 51.5 13.2 70.5 32.5 19.6 20 30.8 46.9 31.2 74.9 0.2 30.2-11.5 58.6-33.2 79.6z">
             <local:MyListItem.Buttons>
                 <x:Array Type="{x:Type local:MyIconButton}">
-                    <local:MyIconButton Tag="5" ToolTip="初始化设置" ToolTipService.Placement="Right" ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1" Click="Reset" Logo="M835.072 184.32l-77.824 81.92c-64.512-66.56-153.6-106.496-251.904-106.496-195.584 0-355.328 159.744-355.328 354.304 0 195.584 159.744 354.304 355.328 354.304 147.456 0 304.128-80.896 337.92-245.76 3.072-15.36-9.216-23.552-21.504-23.552l-72.704 0c-15.36 0-28.672 14.336-29.696 24.576 0 2.048 0 3.072-1.024 3.072-32.768 96.256-107.52 119.808-212.992 119.808-128 0-232.448-104.448-232.448-232.448s104.448-231.424 232.448-231.424c64.512 0 121.856 26.624 165.888 69.632l-101.376 100.352c-1.024 0-1.024 1.024-2.048 1.024-20.48 22.528-12.288 37.888 11.264 37.888l256 0c22.528 0 36.864-15.36 36.864-37.888L871.936 193.536C870.912 174.08 857.6 166.912 835.072 184.32z" LogoScale="0.9" />
+                    <local:MyIconButton Tag="5" ToolTip="初始化设置" ToolTipService.Placement="Right" ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1" Click="Reset" Logo="M530 0c287 0 521 229 521 511s-233 511-521 511c-233 0-436-151-500-368a63 63 0 0 1 44-79 65 65 0 0 1 80 43c48 162 200 276 375 276 215 0 390-171 390-383s-174-383-390-383c-103 0-199 39-270 106l21-5a63 63 0 0 1 33 123l-157 42a65 65 0 0 1-90-42l-49-183a65 65 0 1 1 126-33l6 26A524 524 0 0 1 530 0z" LogoScale="0.9" />
                 </x:Array>
             </local:MyListItem.Buttons>
         </local:MyListItem>
-        <local:MyListItem x:Name="ItemHelp" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="5" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="常见问题"
+        <local:MyListItem x:Name="ItemHelp" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="5" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="常见问题"
                           LogoScale="0.97" Logo="M520.6 620.3c-11.3-0.6-20.1-4-26.9-10.5-6.6-6.3-8.1-11-10.1-20.8-1.9-9.5-1.5-16.7-1-24.9l0.3-5.3c0.5-9.9 3.5-19.6 8.8-28.1 5.7-9 12.5-17.2 20.8-25.1 8.6-8 17.7-15.7 27-22.9 9.4-7.2 18.7-14.8 27.7-22.7 9-7.9 16.2-15.8 22.1-24.3 6.2-9 9.3-18.8 9.3-29.4 1.2-20.3-6.5-37.7-22.8-51.3-16-13.3-37.4-20-63.5-20-13.7 0-26.3 3.3-37.4 10-10.9 6.5-20.2 14.5-27.7 23.8-7.5 9.2-13.5 19-17.9 29.2-4.5 10.6-6.7 19.4-6.7 26.8 0 9.2-3.3 16-10.1 20.8-6.9 4.8-14.6 7.3-22.8 7.3h-1.3c-9-0.3-17-3.3-24.6-9.2-7.3-5.6-11.1-14.6-11.6-27.5-0.6-11.5 1.9-26.1 7.2-43.5 5.4-17.4 14.8-34.4 28.1-50.5 13.4-16.3 31.2-30.4 52.8-41.8 21.6-11.4 49.2-17.2 81.9-17.2 26.4 0 49.6 3.9 69.1 11.7 19.4 7.7 35.4 18.2 47.5 31.1l1.6 1.7c11.8 12.6 20.3 21.7 29.4 44.4 11.6 28.9 7.1 52.1 5.5 58.5-5.8 22.6-12.6 37.7-22.7 50.4-11.7 14.6-24.9 28.2-39.2 40.4-14 11.7-39.2 33.3-39.2 33.3-13.3 11.4-19.4 22.1-18.6 32.9V587c0.5 8.7-2.4 16.1-8.8 22.8-6.2 6.4-14.8 10-26.2 10.5zM519 766.1c-13 0-23.6-4.2-32.2-12.9-8.7-8.7-13-19-13-31.4 0-12.9 4.2-23.5 12.9-32.2 8.7-8.7 19.2-12.9 32.2-12.9 13 0 23.5 4.2 32.2 12.9s12.9 19.2 12.9 32.2c0 12.4-4.2 22.7-12.9 31.4-8.6 8.6-19.2 12.9-32.1 12.9z M515 928.3c-228.1 0-413.7-185.6-413.7-413.7S286.9 100.9 515 100.9s413.7 185.6 413.7 413.7c0.1 228.1-185.5 413.7-413.7 413.7z m0-747.4c-184 0-333.7 149.7-333.7 333.7S331 848.3 515 848.3s333.7-149.7 333.7-333.7S699 180.9 515 180.9z" />
-        <local:MyListItem x:Name="ItemFeedback" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="6" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="关于与反馈"
+        <local:MyListItem x:Name="ItemFeedback" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="6" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="关于与反馈"
                           LogoScale="0.9" Logo="M613.717333 64.426667l3.413334 3.242666 331.861333 331.861334a85.333333 85.333333 0 0 1 3.2 117.269333l-3.2 3.413333L573.162667 896H960a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333H64a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h295.338667L74.069333 610.709333a85.333333 85.333333 0 0 1-3.221333-117.269333l3.2-3.413333 422.4-422.357334a85.333333 85.333333 0 0 1 117.269333-3.221333z m-20.629333 99.882666l-21.418667 321.493334a85.333333 85.333333 0 0 1-74.944 79.04l-4.522666 0.426666-321.493334 21.44 295.552 295.530667 422.4-422.378667L593.066667 164.309333z m-86.442667 13.824L184.533333 500.266667l301.994667-20.117334 20.117333-301.994666z" />
     </StackPanel>
 </local:MyPageLeft>

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

@@ -3,7 +3,7 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:local="clr-namespace:PCL" AnimatedControl="{Binding ElementName=PanItem, Mode=OneWay}">
     <StackPanel Margin="0,12,0,0" Name="PanItem">
-        <local:MyListItem x:Name="ItemHelp" IsScaleAnimationEnabled="False" Checked="True" Type="RadioBox" Tag="0" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="帮助"
+        <local:MyListItem x:Name="ItemHelp" IsScaleAnimationEnabled="False" Checked="True" Type="RadioBox" Tag="0" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="帮助"
                           LogoScale="0.97" Logo="M520.6 620.3c-11.3-0.6-20.1-4-26.9-10.5-6.6-6.3-8.1-11-10.1-20.8-1.9-9.5-1.5-16.7-1-24.9l0.3-5.3c0.5-9.9 3.5-19.6 8.8-28.1 5.7-9 12.5-17.2 20.8-25.1 8.6-8 17.7-15.7 27-22.9 9.4-7.2 18.7-14.8 27.7-22.7 9-7.9 16.2-15.8 22.1-24.3 6.2-9 9.3-18.8 9.3-29.4 1.2-20.3-6.5-37.7-22.8-51.3-16-13.3-37.4-20-63.5-20-13.7 0-26.3 3.3-37.4 10-10.9 6.5-20.2 14.5-27.7 23.8-7.5 9.2-13.5 19-17.9 29.2-4.5 10.6-6.7 19.4-6.7 26.8 0 9.2-3.3 16-10.1 20.8-6.9 4.8-14.6 7.3-22.8 7.3h-1.3c-9-0.3-17-3.3-24.6-9.2-7.3-5.6-11.1-14.6-11.6-27.5-0.6-11.5 1.9-26.1 7.2-43.5 5.4-17.4 14.8-34.4 28.1-50.5 13.4-16.3 31.2-30.4 52.8-41.8 21.6-11.4 49.2-17.2 81.9-17.2 26.4 0 49.6 3.9 69.1 11.7 19.4 7.7 35.4 18.2 47.5 31.1l1.6 1.7c11.8 12.6 20.3 21.7 29.4 44.4 11.6 28.9 7.1 52.1 5.5 58.5-5.8 22.6-12.6 37.7-22.7 50.4-11.7 14.6-24.9 28.2-39.2 40.4-14 11.7-39.2 33.3-39.2 33.3-13.3 11.4-19.4 22.1-18.6 32.9V587c0.5 8.7-2.4 16.1-8.8 22.8-6.2 6.4-14.8 10-26.2 10.5zM519 766.1c-13 0-23.6-4.2-32.2-12.9-8.7-8.7-13-19-13-31.4 0-12.9 4.2-23.5 12.9-32.2 8.7-8.7 19.2-12.9 32.2-12.9 13 0 23.5 4.2 32.2 12.9s12.9 19.2 12.9 32.2c0 12.4-4.2 22.7-12.9 31.4-8.6 8.6-19.2 12.9-32.1 12.9z M515 928.3c-228.1 0-413.7-185.6-413.7-413.7S286.9 100.9 515 100.9s413.7 185.6 413.7 413.7c0.1 228.1-185.5 413.7-413.7 413.7z m0-747.4c-184 0-333.7 149.7-333.7 333.7S331 848.3 515 848.3s333.7-149.7 333.7-333.7S699 180.9 515 180.9z">
             <local:MyListItem.Buttons>
                 <x:Array Type="{x:Type local:MyIconButton}">
@@ -11,13 +11,13 @@
                 </x:Array>
             </local:MyListItem.Buttons>
         </local:MyListItem>
-        <local:MyListItem x:Name="ItemAbout" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="1" PaddingRight="28" Height="36" VerticalAlignment="Top" Title="关于与鸣谢"
+        <local:MyListItem x:Name="ItemAbout" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="1" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="关于与鸣谢"
                           LogoScale="0.98" Logo="M149.883623 873.911618c47.094581 47.094581 101.765247 83.95121 162.783444 109.75085 63.065787 26.618676 130.226755 40.337532 199.230553 40.337532 69.003799 0 136.164767-13.514097 199.230554-40.337532 61.018196-25.79964 115.688862-62.656269 162.783444-109.75085s83.95121-101.765247 109.75085-162.783444c26.618676-63.065787 40.337532-130.226755 40.337532-199.230554 0-69.003799-13.514097-136.164767-40.337532-199.230553-25.79964-61.018196-62.656269-115.688862-109.75085-162.783444-46.889822-46.889822-101.765247-83.95121-162.783444-109.75085C648.062388 13.514097 580.90142 0 511.89762 0s-136.164767 13.514097-199.230553 40.337532c-61.018196 25.79964-115.688862 62.656269-162.783444 109.75085s-83.95121 101.765247-109.75085 162.783444C13.514097 375.732853 0 442.893821 0 511.89762s13.514097 136.164767 40.337532 199.230554c25.594881 61.018196 62.656269 115.688862 109.546091 162.783444zM97.670066 511.89762c0-228.511098 185.921216-414.227554 414.227554-414.227554 228.511098 0 414.227554 185.921216 414.227555 414.227554S740.408718 926.125175 511.89762 926.125175c-228.511098 0-414.227554-185.716457-414.227554-414.227555z M539.130574 244.277544c-8.190362-4.709458-17.609278-7.371326-27.232954-7.371325-9.623675 0-19.042591 2.457109-27.232953 7.371325-16.790242 9.623675-27.232953 27.847231-27.232953 47.299341 0 30.09958 24.366327 54.465907 54.465906 54.465906 30.09958 0 54.465907-24.366327 54.465907-54.465906 0-19.45211-10.442711-37.470906-27.232953-47.299341zM566.363527 732.218356v-284.615077c0-30.09958-24.366327-54.465907-54.465907-54.465906-30.09958 0-54.465907 24.366327-54.465906 54.465906v284.615077c0 30.09958 24.366327 54.465907 54.465906 54.465907 30.09958 0 54.465907-24.366327 54.465907-54.465907z" />
-        <local:MyListItem x:Name="ItemTest" IsScaleAnimationEnabled="False" Tag="2" PaddingRight="35" Type="RadioBox" Height="36" VerticalAlignment="Top" Title="百宝箱"
+        <local:MyListItem x:Name="ItemTest" IsScaleAnimationEnabled="False" Tag="2" MinPaddingRight="35" Type="RadioBox" Height="36" VerticalAlignment="Top" Title="百宝箱"
                           LogoScale="0.97" Logo="M511.488 995.328a128.654222 128.654222 0 0 1-57.116444-13.112889L70.769778 791.808a126.833778 126.833778 0 0 1-70.769778-113.777778V311.608889a126.179556 126.179556 0 0 1 15.36-60.103111V248.604444c1.479111-2.901333 3.356444-5.603556 5.518222-8.021333a127.630222 127.630222 0 0 1 49.891556-42.325333L454.371556 13.368889a128.739556 128.739556 0 0 1 112.981333 0l383.601778 190.407111a126.862222 126.862222 0 0 1 72.049777 113.379556v360.874666a126.805333 126.805333 0 0 1-70.769777 115.939556L568.604444 984.32c-17.92 7.964444-37.461333 11.747556-57.116444 11.008z m42.638222-470.897778v370.204445l360.192-178.545778c14.449778-7.253333 23.552-21.987556 23.438222-38.087111v-335.928889L554.097778 524.430222zM85.248 330.666667v347.335111a42.268444 42.268444 0 0 0 23.438222 38.087111l360.192 178.545778V523.576889L85.248 330.666667zM135.537778 260.835556l375.950222 189.952 137.671111-65.564445L286.435556 188.074667 135.537778 260.864z m245.105778-118.471112l363.576888 197.973334 150.897778-71.480889-365.283555-180.224a42.922667 42.922667 0 0 0-37.518223 0l-111.672888 53.731555z" />
-        <local:MyListItem x:Name="ItemFeedback" IsScaleAnimationEnabled="False" Type="Clickable" Tag="3" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="反馈"
+        <local:MyListItem x:Name="ItemFeedback" IsScaleAnimationEnabled="False" Type="Clickable" Tag="3" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="反馈"
                           LogoScale="0.9" Logo="M613.717333 64.426667l3.413334 3.242666 331.861333 331.861334a85.333333 85.333333 0 0 1 3.2 117.269333l-3.2 3.413333L573.162667 896H960a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333H64a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h295.338667L74.069333 610.709333a85.333333 85.333333 0 0 1-3.221333-117.269333l3.2-3.413333 422.4-422.357334a85.333333 85.333333 0 0 1 117.269333-3.221333z m-20.629333 99.882666l-21.418667 321.493334a85.333333 85.333333 0 0 1-74.944 79.04l-4.522666 0.426666-321.493334 21.44 295.552 295.530667 422.4-422.378667L593.066667 164.309333z m-86.442667 13.824L184.533333 500.266667l301.994667-20.117334 20.117333-301.994666z" />
-        <local:MyListItem x:Name="ItemVote" IsScaleAnimationEnabled="False" Type="Clickable" Tag="4" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="新功能投票"
+        <local:MyListItem x:Name="ItemVote" IsScaleAnimationEnabled="False" Type="Clickable" Tag="4" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="新功能投票"
                           LogoScale="0.92" Logo="M979 1024H45c-24.9 0-45-20.1-45-45s20.1-45 45-45h934c24.9 0 45 20.1 45 45s-20.1 45-45 45zM135 887C60.6 887 0 826.4 0 752V334c0-74.4 60.6-135 135-135s135 60.6 135 135v418c0 74.4-60.6 135-135 135z m0-598c-24.8 0-45 20.2-45 45v418c0 24.8 20.2 45 45 45s45-20.2 45-45V334c0-24.8-20.2-45-45-45zM512 887c-74.4 0-135-60.6-135-135V532c0-74.4 60.6-135 135-135s135 60.6 135 135v220c0 74.4-60.6 135-135 135z m0-400c-24.8 0-45 20.2-45 45v220c0 24.8 20.2 45 45 45s45-20.2 45-45V532c0-24.8-20.2-45-45-45zM889 887c-74.4 0-135-60.6-135-135V135C754 60.6 814.6 0 889 0s135 60.6 135 135v617c0 74.4-60.6 135-135 135z m0-797c-24.8 0-45 20.2-45 45v617c0 24.8 20.2 45 45 45s45-20.2 45-45V135c0-24.8-20.2-45-45-45z" />
     </StackPanel>
 </local:MyPageLeft>

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

@@ -71,7 +71,7 @@
                 CType(ContMenu.FindName("Rename"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Rename_Click))
                 CType(ContMenu.FindName("Refresh"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Refresh_Click))
                 '构建框架与图表按钮
-                Dim NewItem As New MyListItem With {.IsScaleAnimationEnabled = False, .Type = MyListItem.CheckType.RadioBox, .PaddingRight = 30, .Title = Folder.Name, .Info = Folder.Path, .Height = 40, .ContextMenu = ContMenu, .Tag = Folder}
+                Dim NewItem As New MyListItem With {.IsScaleAnimationEnabled = False, .Type = MyListItem.CheckType.RadioBox, .MinPaddingRight = 30, .Title = Folder.Name, .Info = Folder.Path, .Height = 40, .ContextMenu = ContMenu, .Tag = Folder}
                 AddHandler NewItem.Changed, AddressOf FrmSelectLeft.Folder_Change
                 Dim NewIconButton As New MyIconButton With {.Logo = Logo.IconButtonSetup, .LogoScale = 1.1}
                 AddHandler NewIconButton.Click, Sub(sender, e)

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

@@ -111,7 +111,7 @@
         End Try
     End Sub
     Public Shared Function McVersionListItem(Version As McVersion) As MyListItem
-        Dim NewItem As New MyListItem With {.Title = Version.Name, .Info = Version.Info, .Height = 42, .Tag = Version, .PaddingRight = 78, .SnapsToDevicePixels = True, .Type = MyListItem.CheckType.Clickable}
+        Dim NewItem As New MyListItem With {.Title = Version.Name, .Info = Version.Info, .Height = 42, .Tag = Version, .SnapsToDevicePixels = True, .Type = MyListItem.CheckType.Clickable}
         Try
             If Version.Logo.EndsWith("PCL\Logo.png") Then
                 NewItem.Logo = Version.Path & "PCL\Logo.png" '修复老版本中,存储的自定义 Logo 使用完整路径,导致移动后无法加载的 Bug

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


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

@@ -3,17 +3,17 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:local="clr-namespace:PCL" AnimatedControl="{Binding ElementName=PanItem, Mode=OneWay}">
     <StackPanel Margin="0,12,0,0" Name="PanItem">
-        <local:MyListItem x:Name="ItemOverall" IsScaleAnimationEnabled="False" Checked="True" Type="RadioBox" Tag="0" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="概览"
+        <local:MyListItem x:Name="ItemOverall" IsScaleAnimationEnabled="False" Checked="True" Type="RadioBox" Tag="0" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="概览"
                           Logo="M12 0l-11 6v12.131l11 5.869 11-5.869v-12.066l-11-6.065zm-1 21.2l-6.664-3.555 4.201-2.801c1.08-.719-.066-2.359-1.243-1.575l-4.294 2.862v-7.901l8 4.363v8.607zm-6.867-14.63l6.867-3.746v4.426c0 1.323 2 1.324 2 0v-4.415l6.91 3.811-7.905 4.218-7.872-4.294zm8.867 6.03l8-4.269v7.8l-4.263-2.842c-1.181-.785-2.323.855-1.245 1.574l4.172 2.781-6.664 3.556v-8.6z" />
-        <local:MyListItem x:Name="ItemSetup" IsScaleAnimationEnabled="False" Tag="1" PaddingRight="38" Height="36" VerticalAlignment="Top" Title="设置" Type="RadioBox"
+        <local:MyListItem x:Name="ItemSetup" IsScaleAnimationEnabled="False" Tag="1" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="设置" Type="RadioBox"
                           LogoScale="1.02" Logo="M292.693 787.627c0 30.72-24.746 55.466-55.466 55.466s-55.467-24.746-55.467-55.466 24.747-55.467 55.467-55.467c30.72-0.853 55.466 24.747 55.466 55.467z M1000.96 241.493c-2.56-5.973-4.267-11.946-6.827-17.92-0.853-6.826-4.266-12.8-8.533-17.92-7.68-9.386-18.773-15.36-31.573-15.36-6.827 0-13.654 1.707-19.627 5.12l-15.36 15.36-17.067 17.067-21.333 21.333L749.227 381.44c-14.507 14.507-38.4 14.507-52.907 0l-54.613-54.613c-14.507-14.507-14.507-38.4 0-52.907l132.266-132.267 14.507-14.506 20.48-20.48 16.213-16.214c3.414-5.973 5.12-11.946 5.12-19.626 0-12.8-5.973-24.747-16.213-32.427-5.973-4.267-11.947-6.827-18.773-7.68-5.12-2.56-10.24-4.267-16.214-5.973-36.693-12.8-76.8-19.627-117.76-19.627-7.68 0-14.506 0-22.186 0.853-188.587 11.947-337.92 168.107-337.92 360.96 0 16.214 0.853 32.427 3.413 48.64L56.32 663.04c-73.387 74.24-73.387 193.707 0 267.093l33.28 33.28c73.387 73.387 193.707 73.387 267.093 0l240.64-241.493c21.334 4.267 42.667 5.973 64 5.973 190.294 0 346.454-146.773 360.107-333.653 0.853-8.533 0.853-17.92 0.853-27.307 0.854-44.373-6.826-86.186-21.333-125.44zM658.773 639.147c-28.16 0-54.613-4.267-79.36-11.094-4.266 7.68-10.24 14.507-16.213 21.334l-258.56 258.56c-43.52 43.52-115.2 43.52-158.72 0l-29.013-29.014c-43.52-44.373-43.52-115.2 0-158.72l212.48-212.48 46.08-46.08c5.973-5.973 12.8-11.093 19.626-15.36-8.533-26.453-12.8-54.613-12.8-83.626v-1.707c0-152.747 123.734-275.627 276.48-275.627 18.774 0 37.547 1.707 55.467 5.974L597.333 207.36c-52.053 52.053-52.053 137.387 0 189.44l29.867 29.867c52.053 52.053 137.387 52.053 189.44 0L930.987 312.32c2.56 16.213 4.266 33.28 4.266 50.347 0 152.746-123.733 276.48-276.48 276.48z">
             <local:MyListItem.Buttons>
                 <x:Array Type="{x:Type local:MyIconButton}">
-                    <local:MyIconButton Tag="1" ToolTip="初始化" ToolTipService.Placement="Right" ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1" Click="Reset" Logo="M835.072 184.32l-77.824 81.92c-64.512-66.56-153.6-106.496-251.904-106.496-195.584 0-355.328 159.744-355.328 354.304 0 195.584 159.744 354.304 355.328 354.304 147.456 0 304.128-80.896 337.92-245.76 3.072-15.36-9.216-23.552-21.504-23.552l-72.704 0c-15.36 0-28.672 14.336-29.696 24.576 0 2.048 0 3.072-1.024 3.072-32.768 96.256-107.52 119.808-212.992 119.808-128 0-232.448-104.448-232.448-232.448s104.448-231.424 232.448-231.424c64.512 0 121.856 26.624 165.888 69.632l-101.376 100.352c-1.024 0-1.024 1.024-2.048 1.024-20.48 22.528-12.288 37.888 11.264 37.888l256 0c22.528 0 36.864-15.36 36.864-37.888L871.936 193.536C870.912 174.08 857.6 166.912 835.072 184.32z" LogoScale="0.9" />
+                    <local:MyIconButton Tag="1" ToolTip="初始化" ToolTipService.Placement="Right" ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1" Click="Reset" Logo="M530 0c287 0 521 229 521 511s-233 511-521 511c-233 0-436-151-500-368a63 63 0 0 1 44-79 65 65 0 0 1 80 43c48 162 200 276 375 276 215 0 390-171 390-383s-174-383-390-383c-103 0-199 39-270 106l21-5a63 63 0 0 1 33 123l-157 42a65 65 0 0 1-90-42l-49-183a65 65 0 1 1 126-33l6 26A524 524 0 0 1 530 0z" LogoScale="0.9" />
                 </x:Array>
             </local:MyListItem.Buttons>
         </local:MyListItem>
-        <local:MyListItem x:Name="ItemMod" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="2" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="Mod 管理"
+        <local:MyListItem x:Name="ItemMod" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="2" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="Mod 管理"
                           Logo="M789.504 912.896h-195.072a35.328 35.328 0 0 1-34.816-35.328v-77.312a39.936 39.936 0 0 0-40.448-39.936H481.28a40.448 40.448 0 0 0-40.448 39.936v77.312a34.816 34.816 0 0 1-34.816 35.328H204.8a41.984 41.984 0 0 1-40.448-42.496v-200.192a35.328 35.328 0 0 1 35.328-35.328h72.704a39.936 39.936 0 0 0 39.936-39.936v-37.888a39.936 39.936 0 0 0-39.936-40.448H199.68a35.328 35.328 0 0 1-35.328-35.328V287.744A41.984 41.984 0 0 1 204.8 245.76h176.64v-32.768a102.4 102.4 0 0 1 102.4-102.4h33.792a102.4 102.4 0 0 1 102.4 102.4v32.768h170.496a41.984 41.984 0 0 1 41.984 41.984V460.8h28.672a102.4 102.4 0 0 1 102.4 102.4v33.792a102.4 102.4 0 0 1-102.4 102.4h-28.672V870.4a41.984 41.984 0 0 1-43.008 42.496z m-159.744-70.144h131.584V665.6a34.304 34.304 0 0 1 34.816-34.816h64a31.744 31.744 0 0 0 31.744-31.744V563.2a31.744 31.744 0 0 0-31.744-31.744h-64a34.816 34.816 0 0 1-34.816-35.328V316.416h-177.152a35.328 35.328 0 0 1-35.328-35.328V212.992a31.744 31.744 0 0 0-31.744-31.744h-33.792a31.744 31.744 0 0 0-31.744 31.744v68.096a35.328 35.328 0 0 1-34.816 35.328H234.496v130.048h37.888a110.592 110.592 0 0 1 110.08 110.592v37.888a110.592 110.592 0 0 1-110.08 110.592h-37.888v137.216h136.192v-42.496a110.592 110.592 0 0 1 110.592-110.08h37.888a110.592 110.592 0 0 1 110.592 110.08z" >
             <local:MyListItem.Buttons>
                 <x:Array Type="{x:Type local:MyIconButton}">
@@ -21,7 +21,7 @@
                 </x:Array>
             </local:MyListItem.Buttons>
         </local:MyListItem>
-        <local:MyListItem x:Name="ItemModDisabled" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="3" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="Mod 管理"
+        <local:MyListItem x:Name="ItemModDisabled" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="3" MinPaddingRight="35" Height="36" VerticalAlignment="Top" Title="Mod 管理"
                           Logo="M789.504 912.896h-195.072a35.328 35.328 0 0 1-34.816-35.328v-77.312a39.936 39.936 0 0 0-40.448-39.936H481.28a40.448 40.448 0 0 0-40.448 39.936v77.312a34.816 34.816 0 0 1-34.816 35.328H204.8a41.984 41.984 0 0 1-40.448-42.496v-200.192a35.328 35.328 0 0 1 35.328-35.328h72.704a39.936 39.936 0 0 0 39.936-39.936v-37.888a39.936 39.936 0 0 0-39.936-40.448H199.68a35.328 35.328 0 0 1-35.328-35.328V287.744A41.984 41.984 0 0 1 204.8 245.76h176.64v-32.768a102.4 102.4 0 0 1 102.4-102.4h33.792a102.4 102.4 0 0 1 102.4 102.4v32.768h170.496a41.984 41.984 0 0 1 41.984 41.984V460.8h28.672a102.4 102.4 0 0 1 102.4 102.4v33.792a102.4 102.4 0 0 1-102.4 102.4h-28.672V870.4a41.984 41.984 0 0 1-43.008 42.496z m-159.744-70.144h131.584V665.6a34.304 34.304 0 0 1 34.816-34.816h64a31.744 31.744 0 0 0 31.744-31.744V563.2a31.744 31.744 0 0 0-31.744-31.744h-64a34.816 34.816 0 0 1-34.816-35.328V316.416h-177.152a35.328 35.328 0 0 1-35.328-35.328V212.992a31.744 31.744 0 0 0-31.744-31.744h-33.792a31.744 31.744 0 0 0-31.744 31.744v68.096a35.328 35.328 0 0 1-34.816 35.328H234.496v130.048h37.888a110.592 110.592 0 0 1 110.08 110.592v37.888a110.592 110.592 0 0 1-110.08 110.592h-37.888v137.216h136.192v-42.496a110.592 110.592 0 0 1 110.592-110.08h37.888a110.592 110.592 0 0 1 110.592 110.08z" />
     </StackPanel>
 </local:MyPageLeft>

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

@@ -89,7 +89,14 @@
 
     Public Sub Refresh(sender As Object, e As EventArgs) '由边栏按钮匿名调用
         '强制刷新
-        If FrmVersionMod IsNot Nothing Then FrmVersionMod.RefreshList(True) '没有 Else,还没加载刷个鬼的新
+        Try
+            CompProjectCache.Clear()
+            File.Delete(PathTemp & "Cache\LocalMod.json")
+            Log("[Mod] 由于点击刷新按钮,清理本地 Mod 信息缓存")
+        Catch ex As Exception
+            Log(ex, "强制刷新时清理本地 Mod 信息缓存失败")
+        End Try
+        If FrmVersionMod IsNot Nothing Then FrmVersionMod.RefreshList(True) '无需 Else,还没加载刷个鬼的新
         ItemMod.Checked = True
         Hint("正在刷新……", Log:=False)
     End Sub

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

@@ -9,23 +9,21 @@
             <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
                 <StackPanel x:Name="PanMain" Margin="25,10,25,10" Grid.IsSharedSizeScope="True"> <!-- 若显示多选模块则为 92 -->
                     <local:MySearchBox Margin="0,15" HintText="搜索 Mod" x:Name="SearchBox" />
-                    <local:MyCard Grid.Row="0" Margin="0,0,0,15" Title="管理" x:Name="PanManage">
-                        <StackPanel Margin="25,40,15,20">
-                            <TextBlock Margin="0,0,0,11" Text="点击红石灯图标可以快速启用或禁用单个 Mod。将 Mod 文件拖入窗口即可快速安装。" TextWrapping="Wrap" />
-                            <Grid Height="35">
-                                <Grid.ColumnDefinitions>
-                                    <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                                    <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                                    <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                                </Grid.ColumnDefinitions>
-                                <local:MyButton x:Name="BtnManageOpen" Grid.Column="0" MinWidth="140" Text="打开 Mod 文件夹" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                                <local:MyButton x:Name="BtnManageSelectAll" Grid.Column="1" MinWidth="140" Text="全选" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
-                                <local:MyButton x:Name="BtnManageCheck" Grid.Column="2" MinWidth="140" Text="检查 Mod" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ToolTip="快速检查 Mod 的版本要求、重复、前置缺失等问题。&#xa;结果仅供参考,由于部分 Mod 标注的信息有误,可能并不准确。" Visibility="Collapsed" />
-                            </Grid>
-                        </StackPanel>
+                    <local:MyCard Grid.Row="0" Margin="0,0,0,15" x:Name="PanManage">
+                        <Grid Margin="15,15,0,15" Height="35">
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                                <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                                <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                                <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                            </Grid.ColumnDefinitions>
+                            <local:MyButton x:Name="BtnManageOpen" Grid.Column="0" MinWidth="140" Text="打开 Mod 文件夹" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
+                            <local:MyButton x:Name="BtnManageSelectAll" Grid.Column="1" MinWidth="140" Text="全选" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
+                            <local:MyButton x:Name="BtnManageInstall" Grid.Column="2" MinWidth="140" Text="安装 Mod" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ToolTip="将 Mod 文件直接拖入 PCL 窗口即可快捷安装!" ToolTipService.InitialShowDelay="0" />
+                            <local:MyButton x:Name="BtnManageCheck" Grid.Column="3" MinWidth="140" Text="检查 Mod" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ToolTip="快速检查 Mod 的版本要求、重复、前置缺失等问题。&#xa;结果仅供参考,由于部分 Mod 标注的信息有误,可能并不准确。" Visibility="Collapsed" />
+                        </Grid>
                     </local:MyCard>
                     <Grid>
-                        <!--虽然不知道为什么但是删掉这个 Grid 在搜索结束的时候就会出现显示问题-->
                         <local:MyCard x:Name="PanSearch" Visibility="Collapsed" VerticalAlignment="Top" Opacity="0" Margin="0,0,0,15" Title=" " MinHeight="40">
                             <StackPanel Margin="20,40,18,18" Name="PanSearchList" VerticalAlignment="Top" />
                         </local:MyCard>

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

@@ -67,7 +67,7 @@
 
             SearchBox.Text = ""
             '建立 StackPanel
-            PanItems = New StackPanel With {.Margin = New Thickness(18, MyCard.SwapedHeight, 18, If(List.Count > 0, 20, 0)), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)}
+            PanItems = New StackPanel With {.Margin = New Thickness(20, 38, 18, If(List.Count > 0, 20, 0)), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)}
             For Each ModEntity As McMod In List
                 PanItems.Children.Add(McModListItem(ModEntity))
             Next
@@ -95,63 +95,20 @@
         If Counter(McMod.McModState.Unavaliable) > 0 Then TypeList.Add("错误 " & Counter(McMod.McModState.Unavaliable))
         Return "Mod 列表(" & Join(TypeList, ",") & ")"
     End Function
-    Private Function McModListItem(Entry As McMod) As MyListItem
-        '图标
-        Dim Logo As String
-        Select Case Entry.State
-            Case McMod.McModState.Fine
-                Logo = "pack://application:,,,/images/Blocks/RedstoneLampOn.png"
-            Case McMod.McModState.Disabled
-                Logo = "pack://application:,,,/images/Blocks/RedstoneLampOff.png"
-            Case Else '出错
-                Logo = "pack://application:,,,/images/Blocks/RedstoneBlock.png"
-        End Select
-        '文本
-        Dim Title As String
-        If Entry.State = McMod.McModState.Disabled Then
-            Title = GetFileNameWithoutExtentionFromPath(Entry.Path.Substring(0, Entry.Path.Count - ".disabled".Count)) & "(已禁用)"
-        Else
-            Title = GetFileNameWithoutExtentionFromPath(Entry.Path)
-        End If
-        Dim Desc As String
-        If Entry.Version Is Nothing OrElse Entry.Description IsNot Nothing Then
-            Desc = Entry.Name & If(Entry.Version Is Nothing, "", " (" & Entry.Version & ")") & " : " & If(Entry.Description, Entry.Path)
-        ElseIf Entry.IsFileAvailable Then
-            Desc = Entry.Path
-        Else
-            Desc = "存在错误 : " & Entry.Path
-        End If
-        'Desc = If(Entry.ModId, "无可用名称") & " (" & If(Entry.Version, "无可用版本") & ")"
-        'If Entry.Dependencies.Count > 0 Then
-        '    Dim DepList As New List(Of String)
-        '    For Each Dep In Entry.Dependencies
-        '        DepList.Add(Dep.Key & If(Dep.Value Is Nothing, "", "@" & Dep.Value))
-        '    Next
-        '    Desc += " : " & Join(DepList, ", ")
-        'End If
-        '实例化
+    Private Function McModListItem(Entry As McMod) As MyLocalModItem
         AniControlEnabled += 1
-        Dim NewItem As New MyListItem With {
-            .LogoClickable = True, .Logo = Logo, .SnapsToDevicePixels = True, .Title = Title, .Info = Desc, .Height = 42, .Tag = Entry,
-            .Type = MyListItem.CheckType.CheckBox, .IsScaleAnimationEnabled = False,
-            .PaddingRight = 73,
-            .ContentHandler = AddressOf McModContent, .Checked = SelectedMods.Contains(Entry.RawFileName)
-        }
+        Dim NewItem As New MyLocalModItem With {.SnapsToDevicePixels = True, .Entry = Entry,
+            .ButtonHandler = AddressOf McModContent, .Checked = SelectedMods.Contains(Entry.RawFileName)}
+        AddHandler Entry.OnGetCompProject, AddressOf NewItem.Refresh
+        NewItem.Refresh()
         AniControlEnabled -= 1
         Return NewItem
     End Function
-    Private Sub McModContent(sender As MyListItem, e As EventArgs)
-        Dim ModEntity As McMod = sender.Tag
-        '注册点击事件
+    Private Sub McModContent(sender As MyLocalModItem, e As EventArgs)
+        '点击事件
         AddHandler sender.Changed, AddressOf CheckChanged
-        AddHandler sender.LogoClick, AddressOf ItemLogo_Click
+        AddHandler sender.Click, Sub(ss As MyLocalModItem, ee As EventArgs) ss.Checked = Not ss.Checked
         '图标按钮
-        Dim BtnDel As New MyIconButton With {.LogoScale = 1.1, .Logo = Logo.IconButtonDelete, .Tag = sender}
-        BtnDel.ToolTip = "删除"
-        ToolTipService.SetPlacement(BtnDel, Primitives.PlacementMode.Center)
-        ToolTipService.SetVerticalOffset(BtnDel, 30)
-        ToolTipService.SetHorizontalOffset(BtnDel, 2)
-        AddHandler BtnDel.Click, AddressOf Delete_Click
         Dim BtnOpen As New MyIconButton With {.LogoScale = 1.15, .Logo = Logo.IconButtonOpen, .Tag = sender}
         BtnOpen.ToolTip = "打开文件位置"
         ToolTipService.SetPlacement(BtnOpen, Primitives.PlacementMode.Center)
@@ -165,7 +122,7 @@
         ToolTipService.SetHorizontalOffset(BtnCont, 2)
         AddHandler BtnCont.Click, AddressOf Info_Click
         AddHandler sender.MouseRightButtonDown, AddressOf Info_Click
-        sender.Buttons = {BtnCont, BtnOpen, BtnDel}
+        sender.Buttons = {BtnCont, BtnOpen}
     End Sub
 
 #End Region
@@ -206,7 +163,11 @@
     ''' 全选。
     ''' </summary>
     Private Sub BtnManageSelectAll_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageSelectAll.Click
-        ChangeAllSelected(True)
+        If SelectedMods.Count < If(IsSearching, PanSearchList.Children.Count, McModLoader.Output.Count) Then
+            ChangeAllSelected(True)
+        Else
+            ChangeAllSelected(False)
+        End If
     End Sub
 
 #End Region
@@ -217,10 +178,10 @@
     Public SelectedMods As New List(Of String)
 
     '单项切换选择状态
-    Public Sub CheckChanged(sender As MyListItem, e As RouteEventArgs)
+    Public Sub CheckChanged(sender As MyLocalModItem, e As RouteEventArgs)
         If AniControlEnabled <> 0 Then Return
         '更新选择了的内容
-        Dim SelectedKey As String = CType(sender.Tag, McMod).RawFileName
+        Dim SelectedKey As String = sender.Entry.RawFileName
         If sender.Checked Then
             If Not SelectedMods.Contains(SelectedKey) Then SelectedMods.Add(SelectedKey)
         Else
@@ -304,13 +265,13 @@
         SelectedMods.Clear()
         If IsSearching Then
             '搜索中
-            For Each Item As MyListItem In PanSearchList.Children
+            For Each Item As MyLocalModItem In PanSearchList.Children
                 Item.Checked = Value
-                If Value Then SelectedMods.Add(CType(Item.Tag, McMod).RawFileName)
+                If Value Then SelectedMods.Add(Item.Entry.RawFileName)
             Next
             If Not Value Then '只取消选择
                 If PanItems IsNot Nothing Then
-                    For Each Item As MyListItem In PanItems.Children
+                    For Each Item As MyLocalModItem In PanItems.Children
                         Item.Checked = Value
                     Next
                 End If
@@ -318,9 +279,9 @@
         Else
             '非搜索中
             If PanItems IsNot Nothing Then
-                For Each Item As MyListItem In PanItems.Children
+                For Each Item As MyLocalModItem In PanItems.Children
                     Item.Checked = Value
-                    If Value Then SelectedMods.Add(CType(Item.Tag, McMod).RawFileName)
+                    If Value Then SelectedMods.Add(Item.Entry.RawFileName)
                 Next
             End If
         End If
@@ -372,11 +333,12 @@
             End Try
             '更改 Loader 和 UI 中的列表
             Dim NewModEntity As New McMod(NewPath)
+            NewModEntity.Comp = ModEntity.Comp
             Dim IndexOfLoader As Integer = McModLoader.Output.IndexOf(ModEntity)
             McModLoader.Output.RemoveAt(IndexOfLoader)
             McModLoader.Output.Insert(IndexOfLoader, NewModEntity)
             Dim List = If(IsSearching, PanSearchList, PanItems).Children
-            Dim IndexOfUi As Integer = List.IndexOf(List.OfType(Of MyListItem).FirstOrDefault(Function(i) CType(i.Tag, McMod) Is ModEntity))
+            Dim IndexOfUi As Integer = List.IndexOf(List.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry Is ModEntity))
             If IndexOfUi = -1 Then Continue For '因为未知原因 Mod 的状态已经切换完了
             List.RemoveAt(IndexOfUi)
             List.Insert(IndexOfUi, McModListItem(NewModEntity))
@@ -419,7 +381,7 @@
             '更改 Loader 和 UI 中的列表
             McModLoader.Output.Remove(ModEntity)
             Dim Parent As StackPanel = If(IsSearching, PanSearchList, PanItems)
-            Dim IndexOfUi As Integer = Parent.Children.IndexOf(Parent.Children.OfType(Of MyListItem).First(Function(i) CType(i.Tag, McMod) Is ModEntity))
+            Dim IndexOfUi As Integer = Parent.Children.IndexOf(Parent.Children.OfType(Of MyLocalModItem).First(Function(i) i.Entry Is ModEntity))
             Parent.Children.RemoveAt(IndexOfUi)
         Next
         If Not IsSearching Then
@@ -454,165 +416,69 @@
 
 #Region "单个 Mod 项"
 
-    '点击 Logo:切换可用状态 / 显示错误原因
-    Public Sub ItemLogo_Click(sender As MyListItem, e As EventArgs)
-        Try
-
-            Dim ModEntity As McMod = sender.Tag
-            Dim NewPath As String = Nothing
-            Select Case ModEntity.State
-                Case McMod.McModState.Fine
-                    '前置检测警告
-                    If ModEntity.IsPresetMod Then
-                        If MyMsgBox("该 Mod 可能为其他 Mod 的前置,如果禁用可能导致其他 Mod 无法使用。" & vbCrLf & "你确定要继续禁用吗?", "警告", "禁用", "取消") = 2 Then Exit Sub
-                    End If
-                    NewPath = ModEntity.Path & ".disabled"
-                Case McMod.McModState.Disabled
-                    NewPath = ModEntity.Path.Substring(0, ModEntity.Path.Count - ".disabled".Count)
-                Case McMod.McModState.Unavaliable
-                    MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & GetExceptionDetail(ModEntity.FileUnavailableReason), "Mod 读取失败")
-                    Exit Sub
-            End Select
-            '重命名
-            Dim NewModEntity As New McMod(NewPath)
-            Try
-                File.Delete(NewPath)
-                FileSystem.Rename(ModEntity.Path, NewPath)
-            Catch ex As FileNotFoundException
-                Log(ex, "未找到理应存在的 Mod 文件(" & ModEntity.Path & ")")
-                FileSystem.Rename(NewPath, ModEntity.Path)
-                NewModEntity = New McMod(ModEntity.Path)
-            End Try
-            '更改 Loader 中的列表
-            Dim IndexOfLoader As Integer = McModLoader.Output.IndexOf(ModEntity)
-            McModLoader.Output.RemoveAt(IndexOfLoader)
-            McModLoader.Output.Insert(IndexOfLoader, NewModEntity)
-            '更改 UI 中的列表
-            Dim Parent As StackPanel = sender.Parent
-            Dim IndexOfUi As Integer = Parent.Children.IndexOf(sender)
-            Parent.Children.RemoveAt(IndexOfUi)
-            Parent.Children.Insert(IndexOfUi, McModListItem(NewModEntity))
-            '仅在非搜索页面才执行,以确保搜索页内外显示一致
-            If Not IsSearching Then
-                '改变禁用数量的显示
-                CType(Parent.Parent, MyCard).Title = McModGetTitle(McModLoader.Output)
-                '更新加载器状态
-                LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.UpdateOnly)
-            End If
-            RefreshBottomBar()
-
-        Catch ex As Exception
-            Log(ex, "单个状态改变中重命名 Mod 失败")
-            Hint("切换 Mod 状态失败,请尝试关闭正在运行的游戏后再试!")
-            LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.UpdateOnly)
-        End Try
-    End Sub
-    '删除
-    Public Sub Delete_Click(sender As MyIconButton, e As EventArgs)
-        Try
-
-            Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown
-            Dim ListItem As MyListItem = sender.Tag
-            Dim ModEntity As McMod = ListItem.Tag
-            '前置检测警告
-            If ModEntity.IsPresetMod Then
-                If MyMsgBox("该 Mod 可能为其他 Mod 的前置,如果删除可能导致其他 Mod 无法使用。" & vbCrLf & "你确定要继续删除吗?", "警告", "删除", "取消", IsWarn:=True) = 2 Then Exit Sub
-            End If
-            '删除
-            If File.Exists(ModEntity.Path) Then
-                If IsShiftPressed Then
-                    File.Delete(ModEntity.Path)
-                Else
-                    My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
-                End If
-            Else
-                '在不明原因下删除的文件可能还在列表里,如果玩家又点了一次就会出错,总之加个判断也不碍事
-                Log("[System] 需要删除的 Mod 文件不存在(" & ModEntity.Path & ")", LogLevel.Hint)
-                Exit Sub
-            End If
-            '更改 Loader 中的列表
-            McModLoader.Output.Remove(ModEntity)
-            '更改 UI 中的列表
-            Dim Parent As StackPanel = ListItem.Parent
-            Parent.Children.Remove(ListItem)
-            If Parent.Children.Count = 0 Then
-                RefreshList(True)
-            Else
-                CType(Parent.Parent, MyCard).Title = McModGetTitle(McModLoader.Output)
-            End If
-            '显示提示
-            If IsShiftPressed Then
-                Hint("已删除 " & ModEntity.FileName & "!", HintType.Finish)
-            Else
-                Hint("已将 " & ModEntity.FileName & " 删除到回收站!", HintType.Finish)
-            End If
-            LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.UpdateOnly)
-
-        Catch ex As OperationCanceledException
-            Log(ex, "删除 Mod 被主动取消")
-        Catch ex As Exception
-            Log(ex, "删除 Mod 失败", LogLevel.Feedback)
-        End Try
-    End Sub
     '详情
     Public Sub Info_Click(sender As Object, e As EventArgs)
         Try
 
-            Dim ListItem As MyListItem
-            ListItem = If(TypeOf sender Is MyIconButton, sender.Tag, sender)
-            Dim ModEntity As McMod = ListItem.Tag
+            Dim ModEntry As McMod = CType(If(TypeOf sender Is MyIconButton, sender.Tag, sender), MyLocalModItem).Entry
             '加载失败信息
-            If ModEntity.State = McMod.McModState.Unavaliable Then
-                MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & GetExceptionDetail(ModEntity.FileUnavailableReason), "Mod 读取失败")
+            If ModEntry.State = McMod.McModState.Unavaliable Then
+                MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & GetExceptionDetail(ModEntry.FileUnavailableReason), "Mod 读取失败")
                 Return
             End If
-            '获取信息
-            Dim ContentLines As New List(Of String)
-            If ModEntity.Description IsNot Nothing Then ContentLines.Add(ModEntity.Description & vbCrLf)
-            If ModEntity.Authors IsNot Nothing Then ContentLines.Add("作者:" & ModEntity.Authors)
-            ContentLines.Add("文件:" & ModEntity.FileName & "(" & GetString(New FileInfo(ModEntity.Path).Length) & ")")
-            If ModEntity.Version IsNot Nothing Then ContentLines.Add("版本:" & ModEntity.Version)
-            Dim DebugInfo As New List(Of String)
-            If ModEntity.ModId IsNot Nothing Then
-                DebugInfo.Add("Mod ID:" & ModEntity.ModId)
-            End If
-            If ModEntity.Dependencies.Count > 0 Then
-                DebugInfo.Add("依赖于:")
-                For Each Dep In ModEntity.Dependencies
-                    DebugInfo.Add(" - " & Dep.Key & If(Dep.Value Is Nothing, "", ",版本:" & Dep.Value))
-                Next
-            End If
-            If DebugInfo.Count > 0 Then
-                ContentLines.Add("")
-                ContentLines.AddRange(DebugInfo)
-            End If
-            '获取用于搜索的 Mod 名称
-            Dim ModOriginalName As String = ModEntity.Name.Replace(" ", "+")
-            Dim ModSearchName As String = ModOriginalName.Substring(0, 1)
-            For i = 1 To ModOriginalName.Count - 1
-                Dim IsLastLower As Boolean = ModOriginalName(i - 1).ToString.ToLower.Equals(ModOriginalName(i - 1).ToString)
-                Dim IsCurrentLower As Boolean = ModOriginalName(i).ToString.ToLower.Equals(ModOriginalName(i).ToString)
-                If IsLastLower AndAlso Not IsCurrentLower Then
-                    '上一个字母为小写,这一个字母为大写
-                    ModSearchName += "+"
+            If ModEntry.Comp IsNot Nothing Then
+                '跳转到 Mod 下载页面
+                FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail,
+                                   .Additional = {ModEntry.Comp, New List(Of String), PageVersionLeft.Version.Version.McName,
+                                   If(PageVersionLeft.Version.Version.HasForge, CompModLoaderType.Forge, If(PageVersionLeft.Version.Version.HasFabric, CompModLoaderType.Fabric, CompModLoaderType.Any))}})
+            Else
+                '获取信息
+                Dim ContentLines As New List(Of String)
+                If ModEntry.Description IsNot Nothing Then ContentLines.Add(ModEntry.Description & vbCrLf)
+                If ModEntry.Authors IsNot Nothing Then ContentLines.Add("作者:" & ModEntry.Authors)
+                ContentLines.Add("文件:" & ModEntry.FileName & "(" & GetString(New FileInfo(ModEntry.Path).Length) & ")")
+                If ModEntry.Version IsNot Nothing Then ContentLines.Add("版本:" & ModEntry.Version)
+                Dim DebugInfo As New List(Of String)
+                If ModEntry.ModId IsNot Nothing Then
+                    DebugInfo.Add("Mod ID:" & ModEntry.ModId)
                 End If
-                ModSearchName += ModOriginalName(i)
-            Next
-            ModSearchName = ModSearchName.Replace("++", "+").Replace("pti+Fine", "ptiFine")
-            '显示
-            If ModEntity.Url Is Nothing Then
-                If MyMsgBox(Join(ContentLines, vbCrLf), ModEntity.Name, "百科搜索", "返回") = 1 Then
-                    OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0")
+                If ModEntry.Dependencies.Count > 0 Then
+                    DebugInfo.Add("依赖于:")
+                    For Each Dep In ModEntry.Dependencies
+                        DebugInfo.Add(" - " & Dep.Key & If(Dep.Value Is Nothing, "", ",版本:" & Dep.Value))
+                    Next
                 End If
-            Else
-                Select Case MyMsgBox(Join(ContentLines, vbCrLf), ModEntity.Name, "打开官网", "百科搜索", "返回")
-                    Case 1
-                        OpenWebsite(ModEntity.Url)
-                    Case 2
+                If DebugInfo.Count > 0 Then
+                    ContentLines.Add("")
+                    ContentLines.AddRange(DebugInfo)
+                End If
+                '获取用于搜索的 Mod 名称
+                Dim ModOriginalName As String = ModEntry.Name.Replace(" ", "+")
+                Dim ModSearchName As String = ModOriginalName.Substring(0, 1)
+                For i = 1 To ModOriginalName.Count - 1
+                    Dim IsLastLower As Boolean = ModOriginalName(i - 1).ToString.ToLower.Equals(ModOriginalName(i - 1).ToString)
+                    Dim IsCurrentLower As Boolean = ModOriginalName(i).ToString.ToLower.Equals(ModOriginalName(i).ToString)
+                    If IsLastLower AndAlso Not IsCurrentLower Then
+                        '上一个字母为小写,这一个字母为大写
+                        ModSearchName += "+"
+                    End If
+                    ModSearchName += ModOriginalName(i)
+                Next
+                ModSearchName = ModSearchName.Replace("++", "+").Replace("pti+Fine", "ptiFine")
+                '显示
+                If ModEntry.Url Is Nothing Then
+                    If MyMsgBox(Join(ContentLines, vbCrLf), ModEntry.Name, "百科搜索", "返回") = 1 Then
                         OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0")
-                End Select
+                    End If
+                Else
+                    Select Case MyMsgBox(Join(ContentLines, vbCrLf), ModEntry.Name, "打开官网", "百科搜索", "返回")
+                        Case 1
+                            OpenWebsite(ModEntry.Url)
+                        Case 2
+                            OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0")
+                    End Select
+                End If
             End If
-
         Catch ex As Exception
             Log(ex, "获取 Mod 详情失败", LogLevel.Feedback)
         End Try
@@ -621,9 +487,8 @@
     Public Sub Open_Click(sender As MyIconButton, e As EventArgs)
         Try
 
-            Dim ListItem As MyListItem = sender.Tag
-            Dim ModEntity As McMod = ListItem.Tag
-            OpenExplorer("/select,""" & ModEntity.Path & """")
+            Dim ListItem As MyLocalModItem = sender.Tag
+            OpenExplorer("/select,""" & ListItem.Entry.Path & """")
 
         Catch ex As Exception
             Log(ex, "打开 Mod 文件位置失败", LogLevel.Feedback)
@@ -632,6 +497,8 @@
 
 #End Region
 
+#Region "搜索"
+
     Public ReadOnly Property IsSearching As Boolean
         Get
             Return Not String.IsNullOrWhiteSpace(SearchBox.Text)
@@ -643,17 +510,22 @@
             '构造请求
             Dim QueryList As New List(Of SearchEntry(Of McMod))
             For Each Entry As McMod In McModLoader.Output
-                QueryList.Add(New SearchEntry(Of McMod) With {
-                    .Item = Entry,
-                    .SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
-                        New KeyValuePair(Of String, Double)(Entry.Name, 1),
-                        New KeyValuePair(Of String, Double)(Entry.FileName, 1),
-                        New KeyValuePair(Of String, Double)(If(Entry.Description, ""), 0.5)
-                    }
-                })
+                Dim SearchSource As New List(Of KeyValuePair(Of String, Double))
+                SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Name, 1))
+                SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.FileName, 1))
+                If Entry.Version IsNot Nothing Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Version, 0.2))
+                If Entry.Description IsNot Nothing AndAlso Entry.Description <> "" Then
+                    SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Description, 0.4))
+                End If
+                If Entry.Comp IsNot Nothing Then
+                    If Entry.Comp.RawName <> Entry.Name Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.RawName, 1))
+                    If Entry.Comp.TranslatedName <> Entry.Comp.RawName Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.TranslatedName, 1))
+                    If Entry.Comp.Description <> Entry.Description Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.Description, 0.4))
+                End If
+                QueryList.Add(New SearchEntry(Of McMod) With {.Item = Entry, .SearchSource = SearchSource})
             Next
             '进行搜索,构造列表
-            Dim SearchResult = Search(QueryList, SearchBox.Text, MaxBlurCount:=5, MinBlurSimilarity:=0.35)
+            Dim SearchResult = Search(QueryList, SearchBox.Text, MaxBlurCount:=6, MinBlurSimilarity:=0.35)
             PanSearchList.Children.Clear()
             If SearchResult.Count = 0 Then
                 PanSearch.Title = "无搜索结果"
@@ -662,7 +534,7 @@
                 PanSearch.Title = "搜索结果"
                 For Each Result In SearchResult
                     Dim Item = McModListItem(Result.Item)
-                    If ModeDebug Then Item.Info = If(Result.AbsoluteRight, "完全匹配,", "") & "相似度:" & Math.Round(Result.Similarity, 3) & "," & Item.Info
+                    If ModeDebug Then Item.Description = If(Result.AbsoluteRight, "完全匹配,", "") & "相似度:" & Math.Round(Result.Similarity, 3) & "," & Item.Description
                     PanSearchList.Children.Add(Item)
                 Next
                 PanSearchList.Visibility = Visibility.Visible
@@ -692,4 +564,6 @@
         End If
     End Sub
 
+#End Region
+
 End Class

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

@@ -189,6 +189,9 @@
     <Compile Include="Modules\Minecraft\ModJava.vb" />
     <Compile Include="Modules\Minecraft\ModMod.vb" />
     <Compile Include="Modules\Minecraft\ModModpack.vb" />
+    <Compile Include="Modules\Minecraft\MyLocalModItem.xaml.vb">
+      <DependentUpon>MyLocalModItem.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Modules\ModEvent.vb" />
     <Compile Include="Controls\MyExtraButton.xaml.vb">
       <DependentUpon>MyExtraButton.xaml</DependentUpon>
@@ -463,6 +466,10 @@
       <DependentUpon>FormMain.xaml</DependentUpon>
       <SubType>Code</SubType>
     </Compile>
+    <Page Include="Modules\Minecraft\MyLocalModItem.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="Modules\Minecraft\MyCompItem.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
@@ -905,5 +912,9 @@
     <Resource Include="Images\Skins\Sunny.png" />
     <Resource Include="Images\Skins\Zuri.png" />
   </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Images\Icons\Disabled.png" />
+    <Resource Include="Images\Icons\Unavaliable.png" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
 </Project>

BIN
Plain Craft Launcher 2/Resources/Help.zip


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