龙腾猫跃 3 lat temu
rodzic
commit
e6c6975047
41 zmienionych plików z 2616 dodań i 1320 usunięć
  1. 7 3
      Plain Craft Launcher 2/Application.xaml.vb
  2. 9 12
      Plain Craft Launcher 2/Controls/MyButton.xaml.vb
  3. 3 0
      Plain Craft Launcher 2/Controls/MyCard.vb
  4. 1 1
      Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb
  5. 2 2
      Plain Craft Launcher 2/Controls/MyHint.xaml.vb
  6. 9 0
      Plain Craft Launcher 2/Controls/MyIconTextButton.xaml
  7. 180 0
      Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb
  8. 19 11
      Plain Craft Launcher 2/Controls/MyListItem.xaml.vb
  9. 2 2
      Plain Craft Launcher 2/Controls/MyLoading.xaml.vb
  10. 1 1
      Plain Craft Launcher 2/Controls/MyRadioButton.xaml
  11. 3 3
      Plain Craft Launcher 2/Controls/MySearchBox.xaml.vb
  12. 2 2
      Plain Craft Launcher 2/Controls/MyTextButton.vb
  13. 1 1
      Plain Craft Launcher 2/FormMain.xaml
  14. 30 23
      Plain Craft Launcher 2/FormMain.xaml.vb
  15. 4 4
      Plain Craft Launcher 2/Modules/Base/ModBase.vb
  16. 2 2
      Plain Craft Launcher 2/Modules/Base/MyBitmap.vb
  17. 32 15
      Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
  18. 58 45
      Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
  19. 21 16
      Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
  20. 20 10
      Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
  21. 39 915
      Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
  22. 903 0
      Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
  23. 18 10
      Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
  24. 1 1
      Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb
  25. 11 5
      Plain Craft Launcher 2/Modules/ModEvent.vb
  26. 2 2
      Plain Craft Launcher 2/Modules/ModMusic.vb
  27. 4 4
      Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb
  28. 2 2
      Plain Craft Launcher 2/My Project/AssemblyInfo.vb
  29. 25 20
      Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
  30. 19 6
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb
  31. 11 4
      Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
  32. 1 0
      Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb
  33. 1 0
      Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb
  34. 4 0
      Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb
  35. 21 6
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml
  36. 293 84
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
  37. 3 3
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml
  38. 8 0
      Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
  39. 20 12
      Plain Craft Launcher 2/Resources/Custom.xaml
  40. BIN
      Plain Craft Launcher 2/Resources/Help.zip
  41. 824 93
      Plain Craft Launcher 2/Resources/ModData.txt

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

@@ -41,8 +41,11 @@ Public Class Application
                         MsgBox(ex.Message, MsgBoxStyle.Critical, "内存优化失败")
                         Environment.Exit(-1)
                     End Try
-                    Ram = My.Computer.Info.AvailablePhysicalMemory - Ram
-                    Environment.Exit(Math.Max(0, Ram / 1024)) '返回清理的内存量(K)
+                    If My.Computer.Info.AvailablePhysicalMemory < Ram Then '避免 ULong 相减出现负数
+                        Environment.Exit(0)
+                    Else
+                        Environment.Exit((My.Computer.Info.AvailablePhysicalMemory - Ram) / 1024) '返回清理的内存量(K)
+                    End If
 #If DEBUG Then
                 ElseIf e.Args(0) = "--make" Then
                     '制作更新包
@@ -71,7 +74,8 @@ Public Class Application
             Directory.CreateDirectory(PathAppdata)
             '检测单例
 #If Not DEBUG Then
-            Dim WindowHwnd As IntPtr = FindWindow(Nothing, "Plain Craft Launcher 2 ")
+            Dim WindowHwnd As IntPtr = FindWindow(Nothing, "Plain Craft Launcher ")
+            If WindowHwnd = IntPtr.Zero Then FindWindow(Nothing, "Plain Craft Launcher 2 ")
             If WindowHwnd <> IntPtr.Zero Then
                 '将已有的 PCL 窗口拖出来
                 ShowWindowToTop(WindowHwnd)

+ 9 - 12
Plain Craft Launcher 2/Controls/MyButton.xaml.vb

@@ -134,23 +134,21 @@
 
     '实现自定义事件
     Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
-        If IsMouseDown Then
-            Log("[Control] 按下按钮:" & Text)
-            RaiseEvent Click(sender, e)
-            If Not String.IsNullOrEmpty(Tag) Then
-                If Tag.ToString.StartsWith("链接-") OrElse Tag.ToString.StartsWith("启动-") Then
-                    Hint("主页自定义按钮语法已更新,且不再兼容老版本语法,请查看新的自定义示例!")
-                End If
+        If Not IsMouseDown Then Exit Sub
+        Log("[Control] 按下按钮:" & Text)
+        RaiseEvent Click(sender, e)
+        If Not String.IsNullOrEmpty(Tag) Then
+            If Tag.ToString.StartsWith("链接-") OrElse Tag.ToString.StartsWith("启动-") Then
+                Hint("主页自定义按钮语法已更新,且不再兼容老版本语法,请查看新的自定义示例!")
             End If
-            ModEvent.TryStartEvent(EventType, EventData)
         End If
+        ModEvent.TryStartEvent(EventType, EventData)
     End Sub
     Public Property EventType As String
         Get
             Return GetValue(EventTypeProperty)
         End Get
-
-        Set(ByVal value As String)
+        Set(value As String)
             SetValue(EventTypeProperty, value)
         End Set
     End Property
@@ -159,8 +157,7 @@
         Get
             Return GetValue(EventDataProperty)
         End Get
-
-        Set(ByVal value As String)
+        Set(value As String)
             SetValue(EventDataProperty, value)
         End Set
     End Property

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

@@ -166,7 +166,9 @@
     End Sub
 
     '事件
+    Public Property HasMouseAnimation As Boolean = True
     Private Sub MyCard_MouseEnter(sender As Object, e As MouseEventArgs) Handles Me.MouseEnter
+        If Not HasMouseAnimation Then Exit Sub
         Dim AniList As New List(Of AniData)
         If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 150))
         If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 150))
@@ -178,6 +180,7 @@
         AniStart(AniList, "MyCard Mouse " & Uuid)
     End Sub
     Private Sub MyCard_MouseLeave(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
+        If Not HasMouseAnimation Then Exit Sub
         Dim AniList As New List(Of AniData)
         If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 250))
         If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 250))

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

@@ -102,7 +102,7 @@
         Get
             Return _CanRightClick
         End Get
-        Set(ByVal value As Boolean)
+        Set(value As Boolean)
             _CanRightClick = value
         End Set
     End Property

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

@@ -34,7 +34,7 @@
         Get
             Return LabText.Text
         End Get
-        Set(ByVal value As String)
+        Set(value As String)
             LabText.Text = value
         End Set
     End Property
@@ -43,7 +43,7 @@
         Get
             Return BtnClose.Visibility = Visibility.Visible
         End Get
-        Set(ByVal value As Boolean)
+        Set(value As Boolean)
             BtnClose.Visibility = If(value, Visibility.Visible, Visibility.Collapsed)
         End Set
     End Property

+ 9 - 0
Plain Craft Launcher 2/Controls/MyIconTextButton.xaml

@@ -0,0 +1,9 @@
+<Border x:Name="PanBack" x:Class="MyIconTextButton"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             HorizontalAlignment="Center" VerticalAlignment="Center" Background="{StaticResource ColorBrushSemiTransparent}" CornerRadius="13.5" MinHeight="27" MaxHeight="27">
+    <StackPanel Orientation="Horizontal">
+        <Path x:Name="ShapeLogo" HorizontalAlignment="Left" RenderTransformOrigin="0.5,0.5" Margin="12,0,0,0" Stretch="Uniform" Fill="White" MaxHeight="16" MaxWidth="16" VerticalAlignment="Center" />
+        <TextBlock x:Name="LabText" VerticalAlignment="Center" Grid.Column="1" Foreground="White" Margin="7,0,12,1" Grid.ColumnSpan="2" SnapsToDevicePixels="False" UseLayoutRounding="False" />
+    </StackPanel>
+</Border>

+ 180 - 0
Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb

@@ -0,0 +1,180 @@
+Public Class MyIconTextButton
+
+    '基础
+
+    Public Uuid As Integer = GetUuid()
+    Public Event Check(sender As Object, raiseByMouse As Boolean)
+    Public Event Change(sender As Object, raiseByMouse As Boolean)
+    Public Sub RaiseChange()
+        RaiseEvent Change(Me, False)
+    End Sub '使外部程序可以引发本控件的 Change 事件
+
+    '自定义属性
+
+    Public Property Logo As String
+        Get
+            Return ShapeLogo.Data.ToString
+        End Get
+        Set(value As String)
+            ShapeLogo.Data = (New GeometryConverter).ConvertFromString(value)
+        End Set
+    End Property
+    Private _LogoScale As Double = 1
+    Public Property LogoScale() As Double
+        Get
+            Return _LogoScale
+        End Get
+        Set(value As Double)
+            _LogoScale = value
+            If Not IsNothing(ShapeLogo) Then ShapeLogo.RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale}
+        End Set
+    End Property
+
+    Public Property Text As String
+        Get
+            Return GetValue(TextProperty)
+        End Get
+        Set(value As String)
+            SetValue(TextProperty, value)
+        End Set
+    End Property '内容
+    Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(New PropertyChangedCallback(
+                                                                                                                                                               Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
+                                                                                                                                                                   If Not IsNothing(sender) Then CType(sender, MyIconTextButton).LabText.Text = e.NewValue
+                                                                                                                                                               End Sub)))
+    Public Enum ColorState
+        Black
+        Highlight
+    End Enum
+    Private _ColorType As ColorState = ColorState.Black
+    Public Property ColorType As ColorState
+        Get
+            Return _ColorType
+        End Get
+        Set(value As ColorState)
+            _ColorType = value
+            RefreshColor()
+        End Set
+    End Property '颜色类别
+
+    '点击事件
+
+    Public Event Click(sender As Object, e As RouteEventArgs)
+    Private IsMouseDown As Boolean = False
+    Private Sub MyIconTextButton_MouseUp() Handles Me.MouseLeftButtonUp
+        If Not IsMouseDown Then Exit Sub
+        Log("[Control] 按下带图标按钮:" & Text)
+        IsMouseDown = False
+        RaiseEvent Click(Me, New RouteEventArgs(True))
+        ModEvent.TryStartEvent(EventType, EventData)
+        RefreshColor()
+    End Sub
+    Private Sub MyIconTextButton_MouseDown() Handles Me.MouseLeftButtonDown
+        IsMouseDown = True
+        RefreshColor()
+    End Sub
+    Public Property EventType As String
+        Get
+            Return GetValue(EventTypeProperty)
+        End Get
+        Set(value As String)
+            SetValue(EventTypeProperty, value)
+        End Set
+    End Property
+    Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(Nothing))
+    Public Property EventData As String
+        Get
+            Return GetValue(EventDataProperty)
+        End Get
+        Set(value As String)
+            SetValue(EventDataProperty, value)
+        End Set
+    End Property
+    Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(Nothing))
+
+    '动画
+
+    Private Const AnimationTimeOfMouseIn As Integer = 100 '鼠标指向动画长度
+    Private Const AnimationTimeOfMouseOut As Integer = 150 '鼠标移出动画长度
+    Private Sub RefreshColor(Optional obj = Nothing, Optional e = Nothing) Handles Me.MouseEnter, Me.MouseLeave, Me.Loaded, Me.IsEnabledChanged
+        Try
+            If IsLoaded AndAlso AniControlEnabled = 0 AndAlso Not False.Equals(e) Then '防止默认属性变更触发动画,若强制不执行动画,则 e 为 False
+
+                Select Case ColorType
+                    Case ColorState.Black
+                        If IsMouseDown Then
+                            '按下
+                            AniStart(AaColor(Me, BackgroundProperty, "ColorBrush6", 70), "MyIconTextButton Color " & Uuid)
+                        ElseIf IsMouseOver Then
+                            '指向
+                            AniStart({
+                                AaColor(ShapeLogo, Shapes.Path.FillProperty, "ColorBrush3", AnimationTimeOfMouseIn),
+                                AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush3", AnimationTimeOfMouseIn)
+                            }, "MyIconTextButton Checked " & Uuid)
+                            AniStart(AaColor(Me, BackgroundProperty, "ColorBrushBg1", AnimationTimeOfMouseIn), "MyIconTextButton Color " & Uuid)
+                        ElseIf IsEnabled Then
+                            '正常
+                            AniStart({
+                                AaColor(ShapeLogo, Shapes.Path.FillProperty, "ColorBrush1", AnimationTimeOfMouseOut),
+                                AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush1", AnimationTimeOfMouseOut)
+                            }, "MyIconTextButton Checked " & Uuid)
+                            AniStart(AaColor(Me, BackgroundProperty, ColorSemiTransparent - Background, AnimationTimeOfMouseOut), "MyIconTextButton Color " & Uuid)
+                        Else
+                            '禁用
+                            AniStart({
+                                AaColor(ShapeLogo, Shapes.Path.FillProperty, "ColorBrushGray5", 100),
+                                AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrushGray5", 100)
+                            }, "MyIconTextButton Checked " & Uuid)
+                            AniStart(AaColor(Me, BackgroundProperty, ColorSemiTransparent - Background, AnimationTimeOfMouseOut), "MyIconTextButton Color " & Uuid)
+                        End If
+                    Case ColorState.Highlight
+                        If IsMouseDown Then
+                            '按下
+                            AniStart(AaColor(Me, BackgroundProperty, "ColorBrush6", 70), "MyIconTextButton Color " & Uuid)
+                        ElseIf IsMouseOver Then
+                            '指向
+                            AniStart({
+                                AaColor(ShapeLogo, Shapes.Path.FillProperty, "ColorBrush3", AnimationTimeOfMouseIn),
+                                AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush3", AnimationTimeOfMouseIn)
+                            }, "MyIconTextButton Checked " & Uuid)
+                            AniStart(AaColor(Me, BackgroundProperty, "ColorBrushBg1", AnimationTimeOfMouseIn), "MyIconTextButton Color " & Uuid)
+                        ElseIf IsEnabled Then
+                            '正常
+                            AniStart({
+                                AaColor(ShapeLogo, Shapes.Path.FillProperty, "ColorBrush3", AnimationTimeOfMouseOut),
+                                AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush3", AnimationTimeOfMouseOut)
+                            }, "MyIconTextButton Checked " & Uuid)
+                            AniStart(AaColor(Me, BackgroundProperty, ColorSemiTransparent - Background, AnimationTimeOfMouseOut), "MyIconTextButton Color " & Uuid)
+                        Else
+                            '禁用
+                            AniStart({
+                                AaColor(ShapeLogo, Shapes.Path.FillProperty, "ColorBrushGray5", 100),
+                                AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrushGray5", 100)
+                            }, "MyIconTextButton Checked " & Uuid)
+                            AniStart(AaColor(Me, BackgroundProperty, ColorSemiTransparent - Background, AnimationTimeOfMouseOut), "MyIconTextButton Color " & Uuid)
+                        End If
+                End Select
+
+            Else
+
+                '不使用动画
+                AniStop("MyIconTextButton Checked " & Uuid)
+                AniStop("MyIconTextButton Color " & Uuid)
+                Select Case ColorType
+                    Case ColorState.Black
+                        Background = ColorSemiTransparent
+                        ShapeLogo.SetResourceReference(Shapes.Path.FillProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray5"))
+                        LabText.SetResourceReference(TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray5"))
+                    Case ColorState.Highlight
+                        Background = ColorSemiTransparent
+                        ShapeLogo.SetResourceReference(Shapes.Path.FillProperty, If(IsEnabled, "ColorBrush3", "ColorBrushGray5"))
+                        LabText.SetResourceReference(TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush3", "ColorBrushGray5"))
+                End Select
+
+            End If
+        Catch ex As Exception
+            Log(ex, "刷新按钮颜色出错")
+        End Try
+    End Sub
+
+End Class

+ 19 - 11
Plain Craft Launcher 2/Controls/MyListItem.xaml.vb

@@ -15,7 +15,7 @@
             If _RectBack Is Nothing Then
                 Dim Rect As New Border With {
                     .Name = "RectBack",
-                    .CornerRadius = New CornerRadius(If(IsScaleAnimationEnabled, 6, 0)),
+                    .CornerRadius = New CornerRadius(If(IsScaleAnimationEnabled OrElse Height > 40, 6, 0)),
                     .RenderTransform = If(IsScaleAnimationEnabled, New ScaleTransform(0.8, 0.8), Nothing),
                     .RenderTransformOrigin = New Point(0.5, 0.5),
                     .BorderThickness = New Thickness(GetWPFSize(1)),
@@ -225,6 +225,7 @@
                 If _Logo.ToLower.StartsWith("http") Then
                     '网络图片
                     PathLogo = New Image With {
+                            .Tag = Me,
                             .IsHitTestVisible = LogoClickable,
                             .Source = New ImageSourceConverter().ConvertFromString(_Logo),
                             .RenderTransformOrigin = New Point(0.5, 0.5),
@@ -235,6 +236,7 @@
                     '位图
                     Dim Bitmap = New MyBitmap(_Logo)
                     PathLogo = New Canvas With {
+                            .Tag = Me,
                             .IsHitTestVisible = LogoClickable,
                             .Background = Bitmap,
                             .RenderTransformOrigin = New Point(0.5, 0.5),
@@ -252,6 +254,7 @@
                 Else
                     '矢量图
                     PathLogo = New Shapes.Path With {
+                        .Tag = Me,
                         .IsHitTestVisible = LogoClickable, .HorizontalAlignment = HorizontalAlignment.Center, .VerticalAlignment = VerticalAlignment.Center, .Stretch = Stretch.Uniform,
                         .Data = (New GeometryConverter).ConvertFromString(_Logo),
                         .RenderTransformOrigin = New Point(0.5, 0.5),
@@ -267,7 +270,7 @@
                 If LogoClickable Then
                     AddHandler PathLogo.MouseLeave, Sub(sender, e) IsLogoDown = False
                     AddHandler PathLogo.MouseLeftButtonDown, Sub(sender, e) IsLogoDown = True
-                    AddHandler PathLogo.MouseLeftButtonUp, Sub(sender, e) If IsLogoDown Then IsLogoDown = False : RaiseEvent LogoClick(sender, e)
+                    AddHandler PathLogo.MouseLeftButtonUp, Sub(sender, e) If IsLogoDown Then IsLogoDown = False : RaiseEvent LogoClick(sender.Tag, e)
                 End If
             End If
             '改变行距
@@ -286,6 +289,9 @@
     End Property
 
     '图标的点击
+    ''' <summary>
+    ''' 该 Logo 是否可用点击触发事件。需要在 Logo 属性之前设置。
+    ''' </summary>
     Public Property LogoClickable As Boolean = False
     Private IsLogoDown As Boolean = False
 
@@ -447,7 +453,7 @@
                         RectCheck.VerticalAlignment = VerticalAlignment.Center
                         RectCheck.Margin = New Thickness(-1, 0, 0, 0)
                     End If
-                    Anim.Add(AaColor(Me, ForegroundProperty, "ColorBrush3", 200))
+                    Anim.Add(AaColor(Me, ForegroundProperty, If(Height < 40, "ColorBrush3", "ColorBrush2"), 200))
                 Else
                     '由有变无
                     If Not IsNothing(RectCheck) Then
@@ -469,7 +475,7 @@
                         RectCheck.Opacity = 1
                         RectCheck.VerticalAlignment = VerticalAlignment.Stretch
                     End If
-                    SetResourceReference(ForegroundProperty, "ColorBrush3")
+                    SetResourceReference(ForegroundProperty, If(Height < 40, "ColorBrush3", "ColorBrush2"))
                 Else
                     If Not IsNothing(RectCheck) Then
                         RectCheck.Height = 0
@@ -615,12 +621,14 @@
             Else
                 If ButtonStack IsNot Nothing Then Ani.Add(AaOpacity(ButtonStack, -ButtonStack.Opacity, Time * 0.5))
                 Ani.Add(AaOpacity(RectBack, -RectBack.Opacity, Time))
-                If IsScaleAnimationEnabled Then Ani.AddRange({
-                             AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrush7"), Time),
-                             AaScaleTransform(Me, 1 - CType(Me.RenderTransform, ScaleTransform).ScaleX, Time * 3,, New AniEaseOutFluent),
-                             AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time,, New AniEaseOutFluent),
-                             AaScaleTransform(RectBack, -0.246, 1,,, True)
-                         })
+                If IsScaleAnimationEnabled Then
+                    Ani.AddRange({
+                        AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrush7"), Time),
+                        AaScaleTransform(Me, 1 - CType(RenderTransform, ScaleTransform).ScaleX, Time * 3,, New AniEaseOutFluent),
+                        AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time,, New AniEaseOutFluent),
+                        AaScaleTransform(RectBack, -0.246, 1,,, True)
+                    })
+                End If
             End If
             AniStart(Ani, "ListItem Color " & Uuid)
         Else
@@ -647,7 +655,7 @@
 
     Private Sub MyListItem_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
         If Checked Then
-            SetResourceReference(ForegroundProperty, "ColorBrush3")
+            SetResourceReference(ForegroundProperty, If(Height < 40, "ColorBrush3", "ColorBrush2"))
         Else
             SetResourceReference(ForegroundProperty, "ColorBrush1")
         End If

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

@@ -46,7 +46,7 @@ Public Class MyLoading
         Get
             Return _Text
         End Get
-        Set(ByVal value As String)
+        Set(value As String)
             _Text = value
             RefreshText()
         End Set
@@ -57,7 +57,7 @@ Public Class MyLoading
         Get
             Return _TextError
         End Get
-        Set(ByVal value As String)
+        Set(value As String)
             _TextError = value
             RefreshText()
         End Set

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

@@ -4,6 +4,6 @@
              HorizontalAlignment="Center" VerticalAlignment="Center" Background="{StaticResource ColorBrushSemiTransparent}" CornerRadius="13.5" MinHeight="27" MaxHeight="27">
     <StackPanel Orientation="Horizontal">
         <Path x:Name="ShapeLogo" HorizontalAlignment="Left" RenderTransformOrigin="0.5,0.5" Margin="12,0,0,0" Stretch="Uniform" Fill="White" MaxHeight="16" MaxWidth="16" VerticalAlignment="Center" />
-        <TextBlock x:Name="LabText" VerticalAlignment="Center" Grid.Column="1" Foreground="White" Margin="8,0,12,0" Grid.ColumnSpan="2" SnapsToDevicePixels="False" UseLayoutRounding="False" />
+        <TextBlock x:Name="LabText" VerticalAlignment="Center" Grid.Column="1" Foreground="White" Margin="8,0,12,1" Grid.ColumnSpan="2" SnapsToDevicePixels="False" UseLayoutRounding="False" />
     </StackPanel>
 </Border>

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

@@ -1,7 +1,7 @@
 Class MySearchBox
     Inherits MyCard
 
-    Public Event TextChanged(ByVal sender As Object, ByVal e As EventArgs)
+    Public Event TextChanged(sender As Object, e As EventArgs)
     Private Sub MySearchBox_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
         TextBox.Focus()
     End Sub
@@ -11,7 +11,7 @@
         Get
             Return TextBox.HintText
         End Get
-        Set(ByVal value As String)
+        Set(value As String)
             TextBox.HintText = value
         End Set
     End Property
@@ -19,7 +19,7 @@
         Get
             Return TextBox.Text
         End Get
-        Set(ByVal value As String)
+        Set(value As String)
             TextBox.Text = value
         End Set
     End Property

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

@@ -91,7 +91,7 @@
             Return GetValue(EventTypeProperty)
         End Get
 
-        Set(ByVal value As String)
+        Set(value As String)
             SetValue(EventTypeProperty, value)
         End Set
     End Property
@@ -101,7 +101,7 @@
             Return GetValue(EventDataProperty)
         End Get
 
-        Set(ByVal value As String)
+        Set(value As String)
             SetValue(EventDataProperty, value)
         End Set
     End Property

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

@@ -4,7 +4,7 @@
         xmlns:local="clr-namespace:PCL"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
         x:Name="WindMain" x:Class="FormMain" Style="{StaticResource MyWindow}"
-        AllowDrop="True" Topmost="True" Title="Plain Craft Launcher 2 " AllowsTransparency="True" MinHeight="470" MinWidth="810" ScrollViewer.VerticalScrollBarVisibility="Disabled" Icon="/Plain Craft Launcher 2;component/Images/icon.ico" WindowStyle="None" ResizeMode="CanMinimize" Background="{x:Null}" d:DesignWidth="870" d:DesignHeight="520" Width="850" Height="500" RenderTransformOrigin="0.5,0.5">
+        AllowDrop="True" Topmost="True" Title="Plain Craft Launcher " AllowsTransparency="True" MinHeight="470" MinWidth="810" ScrollViewer.VerticalScrollBarVisibility="Disabled" Icon="/Plain Craft Launcher 2;component/Images/icon.ico" WindowStyle="None" ResizeMode="CanMinimize" Background="{x:Null}" d:DesignWidth="870" d:DesignHeight="520" Width="850" Height="500" RenderTransformOrigin="0.5,0.5">
     <Window.TaskbarItemInfo>
         <TaskbarItemInfo ThumbnailClipMargin="{Binding Margin, ElementName=PanForm}" />
     </Window.TaskbarItemInfo>

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

@@ -116,8 +116,13 @@ Public Class FormMain
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '1:BUG-
-        If LastVersion < 297 Then 'Snapshot 2.6.3
+        If LastVersion < 298 Then 'Snapshot 2.6.4
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "Mod 管理中允许多选 Mod 进行批量操作"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化崩溃分析,添加多种崩溃情况的判断"))
+            FeatureCount += 18
+            BugCount += 19
+        End If
+        If LastVersion < 297 Then 'Snapshot 2.6.3
             FeatureCount += 12
             BugCount += 14
         End If
@@ -744,31 +749,32 @@ Public Class FormMain
                             Hint($"输入的 Authlib 验证服务器不符合网址格式({AuthlibServer})!", HintType.Critical)
                             Exit Sub
                         End If
-                        If McVersionCurrent Is Nothing Then
+                        Dim TargetVersion = If(PageCurrent = PageType.VersionSetup, PageVersionLeft.Version, McVersionCurrent)
+                        If TargetVersion Is Nothing Then
                             Hint("请先下载游戏,再设置第三方登录!", HintType.Critical)
                             Exit Sub
                         End If
                         If AuthlibServer = "https://littleskin.cn/api/yggdrasil" Then
                             'LittleSkin
-                            If MyMsgBox($"是否要在版本 {McVersionCurrent.Name} 中开启 LittleSkin 登录?" & vbCrLf &
+                            If MyMsgBox($"是否要在版本 {TargetVersion.Name} 中开启 LittleSkin 登录?" & vbCrLf &
                                         "你可以在 版本设置 → 设置 → 服务器选项 中修改登录方式。", "第三方登录开启确认", "确定", "取消") = 2 Then
                                 Exit Sub
                             End If
-                            Setup.Set("VersionServerLogin", 4, Version:=McVersionCurrent)
-                            Setup.Set("VersionServerAuthServer", "https://littleskin.cn/api/yggdrasil", Version:=McVersionCurrent)
-                            Setup.Set("VersionServerAuthRegister", "https://littleskin.cn/auth/register", Version:=McVersionCurrent)
-                            Setup.Set("VersionServerAuthName", "LittleSkin 登录", Version:=McVersionCurrent)
+                            Setup.Set("VersionServerLogin", 4, Version:=TargetVersion)
+                            Setup.Set("VersionServerAuthServer", "https://littleskin.cn/api/yggdrasil", Version:=TargetVersion)
+                            Setup.Set("VersionServerAuthRegister", "https://littleskin.cn/auth/register", Version:=TargetVersion)
+                            Setup.Set("VersionServerAuthName", "LittleSkin 登录", Version:=TargetVersion)
                         Else
                             '第三方 Authlib 服务器
-                            If MyMsgBox($"是否要在版本 {McVersionCurrent.Name} 中开启第三方登录?" & vbCrLf &
+                            If MyMsgBox($"是否要在版本 {TargetVersion.Name} 中开启第三方登录?" & vbCrLf &
                                         $"登录服务器:{AuthlibServer}" & vbCrLf & vbCrLf &
                                         "你可以在 版本设置 → 设置 → 服务器选项 中修改登录方式。", "第三方登录开启确认", "确定", "取消") = 2 Then
                                 Exit Sub
                             End If
-                            Setup.Set("VersionServerLogin", 4, Version:=McVersionCurrent)
-                            Setup.Set("VersionServerAuthServer", AuthlibServer, Version:=McVersionCurrent)
-                            Setup.Set("VersionServerAuthRegister", AuthlibServer.Replace("api/yggdrasil", "auth/register"), Version:=McVersionCurrent)
-                            Setup.Set("VersionServerAuthName", "", Version:=McVersionCurrent)
+                            Setup.Set("VersionServerLogin", 4, Version:=TargetVersion)
+                            Setup.Set("VersionServerAuthServer", AuthlibServer, Version:=TargetVersion)
+                            Setup.Set("VersionServerAuthRegister", AuthlibServer.Replace("api/yggdrasil", "auth/register"), Version:=TargetVersion)
+                            Setup.Set("VersionServerAuthName", "", Version:=TargetVersion)
                         End If
                         If PageCurrent = PageType.VersionSetup AndAlso PageCurrentSub = PageSubType.VersionSetup Then
                             '正在服务器选项页,需要刷新设置项显示
@@ -916,7 +922,7 @@ Install:
         Get
             Return _Hidden
         End Get
-        Set(ByVal value As Boolean)
+        Set(value As Boolean)
             If _Hidden = value Then Exit Property
             _Hidden = value
             If value Then
@@ -963,6 +969,7 @@ Install:
 #Region "切换页面"
 
     '页面种类与属性
+    '注意,这一枚举在 “切换页面” EventType 中调用,应视作公开 API 的一部分
     ''' <summary>
     ''' 页面种类。
     ''' </summary>
@@ -970,43 +977,43 @@ Install:
         ''' <summary>
         ''' 启动。
         ''' </summary>
-        Launch
+        Launch = 0
         ''' <summary>
         ''' 下载。
         ''' </summary>
-        Download
+        Download = 1
         ''' <summary>
         ''' 联机。
         ''' </summary>
-        Link
+        Link = 2
         ''' <summary>
         ''' 设置。
         ''' </summary>
-        Setup
+        Setup = 3
         ''' <summary>
         ''' 更多。
         ''' </summary>
-        Other
+        Other = 4
         ''' <summary>
         ''' 版本选择。这是一个副页面。
         ''' </summary>
-        VersionSelect
+        VersionSelect = 5
         ''' <summary>
         ''' 下载管理。这是一个副页面。
         ''' </summary>
-        DownloadManager
+        DownloadManager = 6
         ''' <summary>
         ''' 版本设置。这是一个副页面。
         ''' </summary>
-        VersionSetup
+        VersionSetup = 7
         ''' <summary>
         ''' 资源工程详情。这是一个副页面。
         ''' </summary>
-        CompDetail
+        CompDetail = 8
         ''' <summary>
         ''' 帮助详情。这是一个副页面。
         ''' </summary>
-        HelpDetail
+        HelpDetail = 9
     End Enum
     ''' <summary>
     ''' 次要页面种类。其数值必须与 StackPanel 中的下标一致。

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

@@ -11,12 +11,12 @@ Public Module ModBase
 #Region "声明"
 
     '下列版本信息由更新器自动修改
-    Public Const VersionBaseName As String = "2.6.3" '不含分支前缀的显示用版本名
-    Public Const VersionStandardCode As String = "2.6.3." & VersionBranchCode '标准格式的四段式版本号
+    Public Const VersionBaseName As String = "2.6.4" '不含分支前缀的显示用版本名
+    Public Const VersionStandardCode As String = "2.6.4." & VersionBranchCode '标准格式的四段式版本号
 #If BETA Then
-    Public Const VersionCode As Integer = 293 'Release
+    Public Const VersionCode As Integer = 296 'Release
 #Else
-    Public Const VersionCode As Integer = 297 'Snapshot
+    Public Const VersionCode As Integer = 298 'Snapshot
 #End If
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName

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

@@ -73,10 +73,10 @@ Public Class MyBitmap
             Pic = My.Application.TryFindResource(FilePathOrResourceName)
             If Pic Is Nothing Then
                 Pic = New System.Drawing.Bitmap(1, 1)
-                Log(ex, "加载位图失败(" & FilePathOrResourceName & ")")
+                Log(ex, $"加载位图失败({FilePathOrResourceName})")
                 Throw
             Else
-                Log(ex, "指定类型有误的位图加载(" & FilePathOrResourceName & ")", LogLevel.Developer)
+                Log(ex, $"指定类型有误的位图加载({FilePathOrResourceName})", LogLevel.Developer)
                 Exit Try
             End If
         End Try

+ 32 - 15
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb

@@ -375,6 +375,7 @@
                     End Select
                 Next
                 If Tags.Count = 0 Then Tags.Add("杂项")
+                Tags.Sort()
                 ModLoaders.Sort()
 #End Region
             End If
@@ -424,15 +425,32 @@
             Dim ExNameList As List(Of String)
             If TranslatedName = RawName Then
                 '没有中文翻译
-                FinalName = TranslatedName.Split(" | ").First.Split(" - ").First.Split("(").First.Split("[").First
-                ExNameList = TranslatedName.Split({" | ", " - ", "(", ")", "[", "]", "{", "}"}, StringSplitOptions.RemoveEmptyEntries).
-                    Select(Function(s) s.Trim(" /".ToCharArray)).Where(Function(w) Not String.IsNullOrEmpty(w)).ToList
-                ExNameList.RemoveAt(0)
-                If Not ExNameList.Any(Function(s) s.ToLower.Contains("forge") OrElse s.ToLower.Contains("fabric") OrElse s.ToLower.Contains("quilt")) AndAlso '不是标注 XX 版
-                   Not (ExNameList.Count = 1 AndAlso ExNameList.First.ToUpper = ExNameList.First) Then '不是缩写
-                    FinalName = RawName '使用原名
-                    ExNameList.Clear()
-                End If
+                '将所有名称分段
+                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 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
@@ -452,21 +470,20 @@
                     ExNameList = New List(Of String) From {EnglishName} '使用原名
                 End If
                 '添加后缀
-                If Suffix <> "" Then
-                    ExNameList.Add(Suffix)
-                    ExNameList = ExNameList.Distinct().ToList()
-                End If
+                If Suffix <> "" Then ExNameList.Add(Suffix)
             End If
+            ExNameList = ExNameList.Distinct().ToList()
             '显示 FinalName 与 ExNameList
-            NewItem.LabTitle.Text = FinalName
             If ExNameList.Count = 0 Then
 NoExName:
+                NewItem.LabTitle.Text = RawName
                 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 < 19 AndAlso Ex.ToLower.Contains("fabric") AndAlso Ex.ToLower.Contains("forge") Then Continue For
+                    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

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

@@ -365,7 +365,7 @@
         ShadersMod与Optifine同时安装
         Forge安装不完整
         Mod需要Java11
-        Mod依赖项未完善
+        Mod缺少前置或MC版本错误
     End Enum
     ''' <summary>
     ''' 根据 AnalyzeLogs 与可能的版本信息分析崩溃原因。
@@ -374,9 +374,11 @@
         Log("[Crash] 步骤 3:分析崩溃原因")
         LogAll = (If(LogMc, "") & If(LogHs, "") & If(LogCrash, "")).ToLower
 
-        '1. 精准日志匹配 1
+        '1. 精准日志匹配,中/高优先级
         AnalyzeCrit1()
         If CrashReasons.Count > 0 Then GoTo Done
+        AnalyzeCrit2()
+        If CrashReasons.Count > 0 Then GoTo Done
 
         '2. 堆栈分析
         If LogAll.Contains("forge") OrElse LogAll.Contains("fabric") OrElse LogAll.Contains("liteloader") Then
@@ -414,8 +416,8 @@
             Log("[Crash] 可能并未安装 Mod,不进行堆栈分析")
         End If
 
-        '3. 精准日志匹配 2
-        AnalyzeCrit2()
+        '3. 精准日志匹配,低优先级
+        AnalyzeCrit3()
 
         '输出到日志
 Done:
@@ -470,13 +472,12 @@ Done:
             If LogMc.Contains("java.lang.ClassCastException: java.base/jdk") Then AppendReason(CrashReason.使用JDK)
             If LogMc.Contains("java.lang.ClassCastException: class jdk.") Then AppendReason(CrashReason.使用JDK)
             If LogMc.Contains("TRANSFORMER/net.optifine/net.optifine.reflect.Reflector.<clinit>(Reflector.java") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
-            If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.texture.SpriteContents.<init>(net.minecraft.resources.ResourceLocation, ") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
+            If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.texture.SpriteContents.<init>") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
             If LogMc.Contains("java.lang.NoSuchMethodError: 'java.lang.String com.mojang.blaze3d.systems.RenderSystem.getBackendDescription") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
-            If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.block.model.BakedQuad.<init>(int[], int, net.minecraft.core.Direction, net.minecraft.client.renderer.texture.TextureAtlasSprite, boolean, boolean)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容)'1.19.2 Optifine HD_U_H9 Forge 43.2.17
-            If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.server.level.DistanceManager.addRegionTicket(net.minecraft.server.level.TicketType, net.minecraft.world.level.ChunkPos, int, java.lang.Object, boolean)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
-            If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.server.level.DistanceManager.removeRegionTicket(net.minecraft.server.level.TicketType, net.minecraft.world.level.ChunkPos, int, java.lang.Object, boolean)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
-            If LogMc.Contains("java.lang.NoSuchMethodError: 'net.minecraft.network.chat.FormattedText net.minecraft.client.gui.Font.ellipsize(net.minecraft.network.chat.FormattedText, int)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
-            If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraftforge.client.gui.overlay.ForgeGui.renderSelectedItemName(net.minecraft.client.gui.GuiGraphics, int)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容)'1.20.1 OptiFine HD_U_I5 Forge 47.1.28/47.1.14 , 1.20.1 OptiFine_I6_pre1 Forge 47.1.14
+            If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.block.model.BakedQuad.<init>") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
+            If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraftforge.client.gui.overlay.ForgeGui.renderSelectedItemName") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
+            If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.server.level.DistanceManager") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
+            If LogMc.Contains("java.lang.NoSuchMethodError: 'net.minecraft.network.chat.FormattedText net.minecraft.client.gui.Font.ellipsize") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
             If LogMc.Contains("Open J9 is not supported") OrElse LogMc.Contains("OpenJ9 is incompatible") OrElse LogMc.Contains(".J9VMInternals.") Then AppendReason(CrashReason.使用OpenJ9)
             If LogMc.Contains("java.lang.NoSuchFieldException: ucp") Then AppendReason(CrashReason.Java版本过高)
             If LogMc.Contains("because module java.base does not export") Then AppendReason(CrashReason.Java版本过高)
@@ -498,7 +499,7 @@ Done:
             If LogMc.Contains("Invalid module name: '' is not a Java identifier") Then AppendReason(CrashReason.Mod名称包含特殊字符)
             If LogMc.Contains("has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to") Then AppendReason(CrashReason.Mod需要Java11)
             If LogMc.Contains("java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual") Then AppendReason(CrashReason.Mod需要Java11)
-            If LogMc.Contains("java.lang.UnsupportedClassVersionError: net/fabricmc/loader/impl/launch/knot/KnotClient : Unsupported major.minor version") Then AppendReason(CrashReason.Java版本不兼容)
+            If LogMc.Contains("Unsupported major.minor version") Then AppendReason(CrashReason.Java版本不兼容)
             If LogMc.Contains("Invalid maximum heap size") Then AppendReason(CrashReason.使用32位Java导致JVM无法分配足够多的内存)
             If LogMc.Contains("Could not reserve enough space") Then
                 If LogMc.Contains("for 1048576KB object heap") Then
@@ -507,34 +508,17 @@ Done:
                     AppendReason(CrashReason.内存不足)
                 End If
             End If
+            '确定的 Mod 导致崩溃
+            If LogMc.Contains("Caught exception from ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(RegexSeek(LogMc, "(?<=Caught exception from )[^\n]+?")?.TrimEnd((vbCrLf & " ").ToCharArray)))
             'Mod 重复安装
             If LogMc.Contains("DuplicateModsFoundException") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(LogMc, "(?<=\n\t[\w]+ : [A-Z]{1}:[^\n]+(/|\\))[^/\\\n]+?.jar", RegularExpressions.RegexOptions.IgnoreCase))
             If LogMc.Contains("Found a duplicate mod") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "Found a duplicate mod[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase))
             If LogMc.Contains("ModResolutionException: Duplicate") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "ModResolutionException: Duplicate[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase))
-            'Mod 导致的崩溃
-            If LogMc.Contains("Mixin prepare failed ") OrElse LogMc.Contains("Mixin apply failed ") OrElse LogMc.Contains("MixinApplyError") OrElse
-            LogMc.Contains("mixin.injection.throwables.") OrElse LogMc.Contains(".mixins.json] FAILED during )") Then
-                Dim ModId As String = RegexSeek(LogMc, "(?<=in )[^./ ]+(?=.mixins.json.+failed injection check)")
-                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=in mixins.)[^./ ]+(?=.json.+failed injection check)")
-                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in )[^./ ]+(?=.mixins.json)")
-                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in mixins.)[^./ ]+(?=.json)")
-                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[)[^./ ]+(?=.mixins.json\] FAILED during )")
-                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[mixins.)[^./ ]+(?=.json\] FAILED during )")
-                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=from mod )[^./ ]+(?=\] from)")
-                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=for mod )[^./ ]+(?= failed)")
-                '兜底名称判断
-                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "[^./ \]]+(?=.mixins.json")
-                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=mixins.)[^./ \]]+(?=.json")
-                AppendReason(CrashReason.ModMixin失败, TryAnalyzeModName(If(ModId, "").TrimEnd((vbCrLf & " ").ToCharArray)))
-            'Mod 依赖项未完善
-            If LogCrash.Contains("Missing or unsupported mandatory dependencies:") Then
-                AppendReason(CrashReason.Mod依赖项未完善, RegexSearch(LogMc, "(?<=Missing or unsupported mandatory dependencies:)[\n\r]*(\t(.*)[\n\r]*)+"), "").TrimEnd((vbCrLf & " ").ToCharArray)
-            End If
-            If LogMc.Contains("Caught exception from ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(RegexSeek(LogMc, "(?<=Caught exception from )[^\n]+?")?.TrimEnd((vbCrLf & " ").ToCharArray)))
-            '仅当没有其他分析结果时显示
-            If CrashReasons.Count = 0 Then
-                If LogMc.Contains("An exception was thrown, the game will display an error screen and halt.") Then AppendReason(CrashReason.Forge报错, RegexSeek(LogMc, "(?<=the game will display an error screen and halt.[\n\r]+[^\n]+?Exception: )[\s\S]+?(?=\n\tat)")?.Trim(vbCrLf))
-                If LogMc.Contains("A potential solution has been determined:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf))
+            'Mod 缺少前置
+            If LogMc.Contains("Missing or unsupported mandatory dependencies:") Then
+                AppendReason(CrashReason.Mod缺少前置或MC版本错误,
+                    RegexSearch(LogMc, "(?<=Missing or unsupported mandatory dependencies:)([\n\r]+\t(.*))+", RegularExpressions.RegexOptions.IgnoreCase).
+                    Select(Function(s) s.Trim((vbCrLf & vbTab & " ").ToCharArray)).Distinct().ToList())
             End If
         End If
 
@@ -572,14 +556,43 @@ Done:
 
     End Sub
     ''' <summary>
-    ''' 进行精准日志匹配。匹配优先级低于堆栈分析的崩溃。
+    ''' 进行精准日志匹配。匹配优先级高于堆栈分析的崩溃,但低于上面的。
+    ''' 如果第一步已经找到了原因则不执行该检测。
     ''' </summary>
     Private Sub AnalyzeCrit2()
 
         '游戏日志分析
         If LogMc IsNot Nothing Then
-            If Not (LogMc.Contains("at net.") OrElse LogMc.Contains("INFO]")) AndAlso
-               LogHs Is Nothing AndAlso LogCrash Is Nothing AndAlso LogMc.Length < 100 Then
+            'Mixin 崩溃
+            If LogMc.Contains("Mixin prepare failed ") OrElse LogMc.Contains("Mixin apply failed ") OrElse LogMc.Contains("MixinApplyError") OrElse
+               LogMc.Contains("mixin.injection.throwables.") OrElse LogMc.Contains(".mixins.json] FAILED during )") Then
+                Dim ModId As String = RegexSeek(LogMc, "(?<=in )[^./ ]+(?=.mixins.json.+failed injection check)")
+                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=in mixins.)[^./ ]+(?=.json.+failed injection check)")
+                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in )[^./ ]+(?=.mixins.json)")
+                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in mixins.)[^./ ]+(?=.json)")
+                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[)[^./ ]+(?=.mixins.json\] FAILED during )")
+                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[mixins.)[^./ ]+(?=.json\] FAILED during )")
+                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=from mod )[^./ ]+(?=\] from)")
+                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=for mod )[^./ ]+(?= failed)")
+                '兜底名称判断
+                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "[^./ \]]+(?=.mixins.json")
+                If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=mixins.)[^./ \]]+(?=.json")
+                AppendReason(CrashReason.ModMixin失败, TryAnalyzeModName(If(ModId, "").TrimEnd((vbCrLf & " ").ToCharArray)))
+            End If
+            '常规信息
+            If LogMc.Contains("An exception was thrown, the game will display an error screen and halt.") Then AppendReason(CrashReason.Forge报错, RegexSeek(LogMc, "(?<=the game will display an error screen and halt.[\n\r]+[^\n]+?Exception: )[\s\S]+?(?=\n\tat)")?.Trim(vbCrLf))
+            If LogMc.Contains("A potential solution has been determined:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf))
+        End If
+
+    End Sub
+    ''' <summary>
+    ''' 进行精准日志匹配。匹配优先级低于堆栈分析的崩溃。
+    ''' </summary>
+    Private Sub AnalyzeCrit3()
+
+        '游戏日志分析
+        If LogMc IsNot Nothing Then
+            If Not (LogMc.Contains("at net.") OrElse LogMc.Contains("INFO]")) AndAlso LogHs Is Nothing AndAlso LogCrash Is Nothing AndAlso LogMc.Length < 100 Then
                 AppendReason(CrashReason.极短的程序输出, LogMc)
             End If
             'Mixin 失败可以导致大量 Mod 实例创建失败
@@ -814,7 +827,7 @@ NextStack:
                 Case CrashReason.Mod文件被解压
                     Results.Add("由于 Mod 文件被解压了,导致游戏无法继续运行。\n直接把整个 Mod 文件放进 Mod 文件夹中即可,若解压就会导致游戏出错。\n\n请删除 Mod 文件夹中已被解压的 Mod,然后再启动游戏。")
                 Case CrashReason.内存不足
-                    Results.Add("Minecraft 内存不足,导致其无法继续运行。\n这很可能是由于你为游戏分配的内存不足,或是游戏的配置要求过高。\n\n你可以在启动设置中增加为游戏分配的内存,删除配置要求较高的材质、Mod、光影。\n如果这依然不奏效,请在开始游戏前尽量关闭其他软件,或者……换台电脑?\h")
+                    Results.Add("Minecraft 内存不足,导致其无法继续运行。\n这很可能是因为电脑内存不足、游戏分配的内存不足,或是配置要求过高。\n\n你可以尝试在 更多 → 百宝箱 中选择 内存优化,然后再启动游戏。\n如果还是不行,请在启动设置中增加为游戏分配的内存,并删除配置要求较高的材质、Mod、光影。\n如果依然不奏效,请在开始游戏前尽量关闭其他软件,或者……换台电脑?\h")
                 Case CrashReason.使用OpenJ9
                     Results.Add("游戏因为使用 Open J9 而崩溃了。\n请在启动设置的 Java 选择一项中改用非 OpenJ9 的 Java,然后再启动游戏。")
                 Case CrashReason.使用JDK
@@ -837,6 +850,12 @@ NextStack:
                     Else
                         Results.Add("你的游戏遇到了一些问题,这可能是某些 Mod 所引起的,PCL 找到了以下可疑的关键词:\n - " & Join(Additional, ", ") & "\n\n如果你知道这些关键词对应的 Mod,那么有可能就是它引起的错误,你也可以查看错误报告获取详情。\h")
                     End If
+                Case CrashReason.Mod缺少前置或MC版本错误
+                    If Additional.Count > 0 Then
+                        Results.Add("由于未满足 Mod 的依赖项,导致游戏退出。\n未满足的依赖项:\n - " & Join(Additional, "\n - ") & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
+                    Else
+                        Results.Add("由于未满足 Mod 的依赖项,导致游戏退出。\n请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h")
+                    End If
                 Case CrashReason.崩溃日志堆栈分析发现Mod名称
                     If Additional.Count = 1 Then
                         Results.Add("名为 " & Additional.First & " 的 Mod 可能导致了游戏出错。\n\e\h")
@@ -943,12 +962,6 @@ NextStack:
                     Else
                         Results.Add("Forge 可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h")
                     End If
-                Case CrashReason.Mod依赖项未完善
-                    If Additional.Count = 1 Then
-                        Results.Add("由于 Mod 缺少或不受支持的必备依赖项,导致了游戏退出。\n以下是未满足条件的必备依赖项:" & Additional.First & "\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
-                    Else
-                        Results.Add("由于 Mod 缺少或不受支持的强制依赖项,导致了游戏退出。请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h")
-                    End If
                 Case CrashReason.没有可用的分析文件
                     Results.Add("你的游戏出现了一些问题,但 PCL 未能找到相关记录文件,因此无法进行分析。\h")
                 Case Else

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

@@ -1,5 +1,5 @@
 Public Module ModJava
-    Public JavaListCacheVersion As Integer = 4
+    Public JavaListCacheVersion As Integer = 5
 
     ''' <summary>
     ''' 目前所有可用的 Java。
@@ -129,8 +129,10 @@
                 End If
                 Is64Bit = Output.Contains("64-bit")
                 If Version.Minor <= 4 OrElse Version.Minor >= 25 Then Throw New Exception("分析详细信息失败,获取的版本为 " & Version.ToString)
-                '无论如何不允许使用 JRE 16,它完全是炸的,安 OptiFine、Forge 和启动 MC 都会炸
-                If IsJre AndAlso VersionCode = 16 Then Throw New Exception("由于 JRE 16 对 Minecraft 的兼容性很差,因此不再允许使用")
+                '基于 #2249 发现的 JRE 17 似乎也导致了 Forge 安装失败,干脆禁用更多版本的 JRE
+                If IsJre AndAlso VersionCode >= 16 Then Throw New Exception("由于高版本 JRE 对 Minecraft 的兼容性很差,因此不再允许使用")
+                ''无论如何不允许使用 JRE 16,它完全是炸的,安 OptiFine、Forge 和启动 MC 都会炸
+                'If IsJre AndAlso VersionCode = 16 Then Throw New Exception("由于 JRE 16 对 Minecraft 的兼容性很差,因此不再允许使用")
             Catch ex As Exception
                 Log("[Java] 检查失败的 Java 输出:" & PathFolder & "java.exe" & vbCrLf & If(Output, "无程序输出"))
                 Throw New Exception("检查 Java 失败(" & If(PathJavaw, "Nothing") & ")", ex)
@@ -230,6 +232,8 @@
                 Try
                     UserJava = JavaEntry.FromJson(GetJson(Setup.Get("VersionArgumentJavaSelect", Version:=RelatedVersion)))
                     UserJava.Check()
+                Catch ex As ThreadInterruptedException
+                    Throw
                 Catch ex As Exception
                     UserJava = Nothing
                     Setup.Reset("VersionArgumentJavaSelect", Version:=RelatedVersion)
@@ -242,6 +246,8 @@
                 Try
                     UserJava = JavaEntry.FromJson(GetJson(Setup.Get("LaunchArgumentJavaSelect")))
                     UserJava.Check()
+                Catch ex As ThreadInterruptedException
+                    Throw
                 Catch ex As Exception
                     UserJava = Nothing
                     Setup.Reset("LaunchArgumentJavaSelect")
@@ -450,7 +456,7 @@ NoUserJava:
         '4. Java 大版本
         If Left.VersionCode <> Right.VersionCode Then
             '                             Java  7   8   9  10  11  12 13 14 15  16  17  18  19  20...
-            Dim Weight = {0, 1, 2, 3, 4, 5, 6, 14, 29, 10, 12, 15, 13, 9, 8, 7, 11, 31, 30, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28}
+            Dim Weight = {0, 1, 2, 3, 4, 5, 6, 14, 30, 10, 12, 15, 13, 9, 8, 7, 11, 31, 29, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28}
             Return Weight.ElementAtOrDefault(Left.VersionCode) >= Weight.ElementAtOrDefault(Right.VersionCode)
         End If
         '5. 最次级版本号更接近 51
@@ -530,10 +536,10 @@ NoUserJava:
             For Each Pair In JavaPreList
                 If Pair.Key.Contains("javapath_target_") OrElse Pair.Key.Contains("javatmp") Then
                     Log("[Java] 位于 " & Pair.Key & " 的 Java 包含特殊引用")
-                    Continue For
+                Else
+                    Log("[Java] 位于 " & Pair.Key & " 的 Java 不含特殊引用")
+                    JavaWithoutInherit.Add(Pair.Key, Pair.Value)
                 End If
-                Log("[Java] 位于 " & Pair.Key & " 的 Java 不含特殊引用")
-                JavaWithoutInherit.Add(Pair.Key, Pair.Value)
             Next
             If JavaWithoutInherit.Count > 0 Then JavaPreList = JavaWithoutInherit
 
@@ -688,18 +694,17 @@ Wait:
 
     ''' <summary>
     ''' 提示 Java 缺失,并弹窗确认是否自动下载。返回玩家选择是否下载。
-    ''' 支持下载 Java 8、14、17,输入其他 VersionCode 会要求玩家自行下载。
     ''' </summary>
-    Public Function JavaDownloadConfirm(VersionCode As String) As Boolean
-        If VersionCode = 8 OrElse VersionCode = 14 OrElse VersionCode = 17 Then
-            Return MyMsgBox($"PCL 未找到 Java {VersionCode},是否需要 PCL 自动下载?" & vbCrLf &
-                            $"如果你已经安装了 Java {VersionCode},请在 设置 → 启动选项 → 游戏 Java 中手动导入。",
-                            "未找到 Java", "自动下载", "取消") = 1
-        Else
-            MyMsgBox($"PCL 未找到 Java {VersionCode}。" & vbCrLf &
-                     $"请自行搜索并安装 Java {VersionCode},安装后在 设置 → 启动选项 → 游戏 Java 中重新搜索或导入。",
+    Public Function JavaDownloadConfirm(VersionDescription As String, Optional ForcedManualDownload As Boolean = False) As Boolean
+        If ForcedManualDownload Then
+            MyMsgBox($"PCL 未找到 {VersionDescription}。" & vbCrLf &
+                     $"请自行搜索并安装 {VersionDescription},安装后在 设置 → 启动选项 → 游戏 Java 中重新搜索或导入。",
                      "未找到 Java")
             Return False
+        Else
+            Return MyMsgBox($"PCL 未找到 {VersionDescription},是否需要 PCL 自动下载?" & vbCrLf &
+                            $"如果你已经安装了 {VersionDescription},请在 设置 → 启动选项 → 游戏 Java 中手动导入。",
+                            "未找到 Java", "自动下载", "取消") = 1
         End If
     End Function
 

+ 20 - 10
Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb

@@ -1131,11 +1131,11 @@ SystemBrowser:
             If McVersionCurrent.Version.McName = "1.7.2" Then
                 '1.7.2:必须 Java 7
                 MinVer = New Version(1, 7, 0, 0) : MaxVer = New Version(1, 7, 999, 999)
-            ElseIf McVersionCurrent.Version.McCodeMain <= 12 AndAlso McVersionCurrent.Version.McCodeMain > 0 AndAlso VersionSortBoolean("14.23.5.2855", McVersionCurrent.Version.ForgeVersion) Then
-                '<=1.12,Forge 14.23.5.2855 及更低:Java 8
+            ElseIf McVersionCurrent.Version.McCodeMain <= 12 AndAlso McVersionCurrent.Version.McCodeMain > 0 Then
+                '<=1.12:Java 8
                 MaxVer = New Version(1, 8, 999, 999)
-            ElseIf McVersionCurrent.Version.McCodeMain <= 14 AndAlso McVersionCurrent.Version.McCodeMain >= 13 AndAlso VersionSortBoolean("28.2.23", McVersionCurrent.Version.ForgeVersion) Then
-                '1.13 - 1.14,Forge 28.2.23 及更低:Java 8 - 10
+            ElseIf McVersionCurrent.Version.McCodeMain <= 14 AndAlso McVersionCurrent.Version.McCodeMain >= 13 Then
+                '1.13 - 1.14:Java 8 - 10
                 MinVer = New Version(1, 8, 0, 0) : MaxVer = New Version(1, 10, 999, 999)
             ElseIf McVersionCurrent.Version.McCodeMain = 15 Then
                 '1.15:Java 8 - 15
@@ -1182,17 +1182,27 @@ SystemBrowser:
         If Task.IsAborted Then Exit Sub '中断加载会导致 JavaSelect 异常地返回空值,误判找不到 Java
         McLaunchLog("无合适的 Java,需要确认是否自动下载")
         Dim JavaCode As String
-        If MinVer >= New Version(1, 16, 0, 0) Then
+        If MinVer >= New Version(1, 9, 0, 0) Then
             JavaCode = 17
+            If Not JavaDownloadConfirm("Java 17") Then Throw New Exception("$$")
         ElseIf MaxVer < New Version(1, 8, 0, 0) Then
             JavaCode = 7
-        ElseIf MinVer > New Version(1, 8, 0, 52) Then
-            '缺少 Java 8 较新版(统一通行证)
+            If Not JavaDownloadConfirm("Java 7") Then Throw New Exception("$$")
+        ElseIf MinVer > New Version(1, 8, 0, 100) AndAlso MaxVer < New Version(1, 8, 0, 321) Then
             JavaCode = "8u101"
+            JavaDownloadConfirm("Java 8.0.101 ~ 8.0.320", True)
+            Throw New Exception("$$")
+        ElseIf MinVer > New Version(1, 8, 0, 100) Then
+            JavaCode = "8u101"
+            JavaDownloadConfirm("Java 8.0.101 或更高版本的 Java 8", True)
+            Throw New Exception("$$")
+        ElseIf MaxVer < New Version(1, 8, 0, 321) Then
+            JavaCode = 8
+            If Not JavaDownloadConfirm("Java 8.0.320 或更低版本的 Java 8") Then Throw New Exception("$$")
         Else
             JavaCode = 8
+            If Not JavaDownloadConfirm("Java 8") Then Throw New Exception("$$")
         End If
-        If Not JavaDownloadConfirm(JavaCode) Then Throw New Exception("$$")
 
         '开始自动下载
         Dim JavaLoader = JavaFixLoaders(JavaCode)
@@ -1350,7 +1360,7 @@ SystemBrowser:
 
         '添加 Java Wrapper 作为主 Jar
         If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
-        DataList.Insert(0, "-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """")
+        DataList.Add("-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """")
         DataList.Add("-jar """ & ExtractJavaWrapper() & """")
 
         '添加 MainClass
@@ -1417,7 +1427,7 @@ NextVersion:
 
         '添加 Java Wrapper 作为主 Jar
         If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
-        DataList.Insert(0, "-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """")
+        DataList.Add("-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """")
         DataList.Add("-jar """ & ExtractJavaWrapper() & """")
 
         '将 "-XXX" 与后面 "XXX" 合并到一起

+ 39 - 915
Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb

@@ -1,4 +1,5 @@
 Imports System.IO.Compression
+Imports NAudio.Gui
 
 Public Module ModMinecraft
 
@@ -392,7 +393,7 @@ Public Module ModMinecraft
                         '非准确的版本判断警告
                         Log("[Minecraft] 无法完全确认 MC 版本号的版本:" & Name)
                         '从文件夹名中获取
-                        Regex = RegexSeek(Name, "([0-9w]{5}[a-z]{1})|(1\.[0-9]+(\.[0-9]+)?(-(pre|rc)[1-9]?| Pre-Release( [1-9]{1})?)?)")
+                        Regex = RegexSeek(Name, "([0-9w]{5}[a-z]{1})|(1\.[0-9]+(\.[0-9]+)?(-(pre|rc)[1-9]?| Pre-Release( [1-9]{1})?)?)", RegularExpressions.RegexOptions.IgnoreCase)
                         If Regex IsNot Nothing Then
                             _Version.McName = Regex
                             GoTo VersionSearchFinish
@@ -401,7 +402,7 @@ Public Module ModMinecraft
                         Dim JsonRaw As JObject = JsonObject.DeepClone()
                         JsonRaw.Remove("libraries")
                         Dim JsonRawText As String = JsonRaw.ToString
-                        Regex = RegexSeek(JsonRawText, "([0-9w]{5}[a-z]{1})|(1\.[0-9]+(\.[0-9]+)?(-(pre|rc)[1-9]?| Pre-Release( [1-9]{1})?)?)")
+                        Regex = RegexSeek(JsonRawText, "([0-9w]{5}[a-z]{1})|(1\.[0-9]+(\.[0-9]+)?(-(pre|rc)[1-9]?| Pre-Release( [1-9]{1})?)?)", RegularExpressions.RegexOptions.IgnoreCase)
                         If Regex IsNot Nothing Then
                             _Version.McName = Regex
                             GoTo VersionSearchFinish
@@ -467,7 +468,7 @@ VersionSearchFinish:
                 End If
                 Return _JsonText
             End Get
-            Set(ByVal value As String)
+            Set(value As String)
                 _JsonText = value
             End Set
         End Property
@@ -562,7 +563,7 @@ Recheck:
                 End If
                 Return _JsonObject
             End Get
-            Set(ByVal value As JObject)
+            Set(value As JObject)
                 _JsonObject = value
             End Set
         End Property
@@ -966,7 +967,7 @@ ExitDataLoad:
                 End If
                 Return _SortCode
             End Get
-            Set(ByVal value As Integer)
+            Set(value As Integer)
                 _SortCode = value
             End Set
         End Property
@@ -1608,7 +1609,7 @@ NextVersion:
             Get
                 Return _Url
             End Get
-            Set(ByVal value As String)
+            Set(value As String)
                 '孤儿 Forge 作者喜欢把没有的 URL 写个空字符串
                 _Url = If(String.IsNullOrWhiteSpace(value), Nothing, value)
             End Set
@@ -2122,30 +2123,46 @@ NextVersion:
 
             '确认 Virtual 与 Map 状态
             Dim IsVirtual As Boolean = False
-            If Json("virtual") IsNot Nothing Then IsVirtual = Json("virtual").ToString
-            Dim IsMap As Boolean = False
-            If Json("map_to_resources") IsNot Nothing Then IsMap = Json("map_to_resources").ToString
+            If Json("virtual") IsNot Nothing AndAlso Json("virtual").ToString Then IsVirtual = True
+            If Json("map_to_resources") IsNot Nothing AndAlso Json("map_to_resources").ToString Then
+                IsVirtual = True
+                '刷新 resources 文件夹符号链接(#2182)
+                'Dim Info As FileSystemInfo = New FileInfo(PathMcFolder & "resources\pack.mcmeta")
+                'If Info.Attributes.HasFlag(FileAttributes.ReparsePoint) Then
+                If Not File.Exists(PathMcFolder & "resources\pack.mcmeta") Then
+                    Log("[Minecraft] 尝试刷新 resources 文件夹符号链接", LogLevel.Debug)
+                    Try
+                        DeleteDirectory(PathMcFolder & "resources\", True)
+                        Directory.CreateDirectory(PathMcFolder & "assets\virtual\legacy\")
+                        Dim Result = ShellAndGetOutput("cmd", $"/C mklink /D /J ""{PathMcFolder}resources"" ""{PathMcFolder}assets\virtual\legacy""")
+                        Log($"[Minecraft] 符号链接创建结果:{Result}")
+                        If Not Result.Contains("<<===>>") Then Throw New Exception($"非预期的结果({Result})")
+                    Catch ex As Exception
+                        Log(ex, "创建资源文件夹链接失败,游戏可能会没有声音", LogLevel.Msgbox)
+                    End Try
+                End If
+            End If
 
             '加载列表
-            If IsVirtual OrElse IsMap Then
+            If IsVirtual Then
                 For Each File As JProperty In Json("objects").Children
                     McAssetsListGet.Add(New McAssetsToken With {
-                                        .IsVirtual = True,
-                                        .LocalPath = PathMcFolder & "assets\virtual\legacy\" & File.Name.Replace("/", "\"),
-                                        .SourcePath = File.Name,
-                                        .Hash = File.Value("hash").ToString,
-                                        .Size = File.Value("size").ToString
-                                    })
+                        .IsVirtual = True,
+                        .LocalPath = PathMcFolder & "assets\virtual\legacy\" & File.Name.Replace("/", "\"),
+                        .SourcePath = File.Name,
+                        .Hash = File.Value("hash").ToString,
+                        .Size = File.Value("size").ToString
+                    })
                 Next
             Else
                 For Each File As JProperty In Json("objects").Children
                     McAssetsListGet.Add(New McAssetsToken With {
-                                        .IsVirtual = False,
-                                        .LocalPath = PathMcFolder & "assets\objects\" & Left(File.Value("hash").ToString, 2) & "\" & File.Value("hash").ToString,
-                                        .SourcePath = File.Name,
-                                        .Hash = File.Value("hash").ToString,
-                                        .Size = File.Value("size").ToString
-                                    })
+                        .IsVirtual = False,
+                        .LocalPath = PathMcFolder & "assets\objects\" & Left(File.Value("hash").ToString, 2) & "\" & File.Value("hash").ToString,
+                        .SourcePath = File.Name,
+                        .Hash = File.Value("hash").ToString,
+                        .Size = File.Value("size").ToString
+                    })
                 Next
             End If
 
@@ -2186,899 +2203,6 @@ NextVersion:
         Return Result
     End Function
 
-#End Region
-
-#Region "Mod"
-
-    Public Class McMod
-
-#Region "基础"
-
-        ''' <summary>
-        ''' Mod 文件的地址。
-        ''' </summary>
-        Public ReadOnly Path As String
-        Public Sub New(Path As String)
-            Me.Path = If(Path, "")
-        End Sub
-
-        ''' <summary>
-        ''' Mod 的完整文件名。
-        ''' </summary>
-        Public ReadOnly Property FileName As String
-            Get
-                Return GetFileNameFromPath(Path)
-            End Get
-        End Property
-
-        ''' <summary>
-        ''' Mod 的状态。
-        ''' </summary>
-        Public ReadOnly Property State As McModState
-            Get
-                Load()
-                If Not IsFileAvailable Then
-                    Return McModState.Unavaliable
-                ElseIf Path.EndsWith(".disabled") Then
-                    Return McModState.Disabled
-                Else
-                    Return McModState.Fine
-                End If
-            End Get
-        End Property
-        Public Enum McModState As Integer
-            Fine = 0
-            Disabled = 1
-            Unavaliable = 2
-        End Enum
-
-#End Region
-
-#Region "信息项"
-
-        ''' <summary>
-        ''' Mod 的名称。若不可用则为 ModID 或无扩展的文件名。
-        ''' </summary>
-        Public Property Name As String
-            Get
-                If _Name Is Nothing Then Load()
-                If _Name Is Nothing Then _Name = _ModId
-                If _Name Is Nothing Then _Name = GetFileNameWithoutExtentionFromPath(Path)
-                Return _Name
-            End Get
-            Set(value As String)
-                If _Name Is Nothing AndAlso value IsNot Nothing AndAlso
-                   value.ToLower <> "name" AndAlso value.Count > 1 AndAlso Val(value).ToString <> value Then
-                    _Name = value
-                End If
-            End Set
-        End Property
-        Private _Name As String = Nothing
-
-        ''' <summary>
-        ''' Mod 的描述信息。
-        ''' </summary>
-        Public Property Description As String
-            Get
-                If _Description Is Nothing Then Load()
-                If _Description Is Nothing AndAlso FileUnavailableReason IsNot Nothing Then _Description = FileUnavailableReason.Message
-                'If _Description Is Nothing Then _Description = Path
-                Return _Description
-            End Get
-            Set(value As String)
-                If _Description Is Nothing AndAlso value IsNot Nothing AndAlso value.Count > 2 Then
-                    _Description = value.ToString.Trim(vbLf)
-                    '优化显示:若以 [a-zA-Z0-9] 结尾,加上小数点句号
-                    If _Description.ToLower.LastIndexOfAny("qwertyuiopasdfghjklzxcvbnm0123456789") = _Description.Count - 1 Then _Description += "."
-                End If
-            End Set
-        End Property
-        Private _Description As String = Nothing
-
-        ''' <summary>
-        ''' Mod 的版本,不保证符合版本格式规范。
-        ''' </summary>
-        Public Property Version As String
-            Get
-                If _Version Is Nothing Then Load()
-                Return _Version
-            End Get
-            Set(value As String)
-                If _Version IsNot Nothing AndAlso (_Version.Contains(".") OrElse _Version.Contains("-")) Then Exit Property
-                If value IsNot Nothing AndAlso value.ToLower.Contains("version") Then value = "version" '需要修改的标识
-                _Version = value
-            End Set
-        End Property
-        Private _Version As String = Nothing
-
-        ''' <summary>
-        ''' 用于依赖检查的 ModID。
-        ''' </summary>
-        Public Property ModId As String
-            Get
-                If _ModId Is Nothing Then Load()
-                Return _ModId
-            End Get
-            Set(value As String)
-                If value Is Nothing Then Exit Property
-                value = RegexSeek(value.ToLower, "[0-9a-z_]+")
-                If value IsNot Nothing AndAlso value.ToLower <> "name" AndAlso value.Count > 1 AndAlso Val(value).ToString <> value Then
-                    If Not PossibleModId.Contains(value) Then PossibleModId.Add(value)
-                    If _ModId Is Nothing Then _ModId = value
-                End If
-            End Set
-        End Property
-        Private _ModId As String = Nothing
-        ''' <summary>
-        ''' 其他可能的 ModID。
-        ''' </summary>
-        Public PossibleModId As New List(Of String)
-
-        ''' <summary>
-        ''' Mod 的主页。
-        ''' </summary>
-        Public Property Url As String
-            Get
-                If _Url Is Nothing Then Load()
-                Return _Url
-            End Get
-            Set(value As String)
-                If _Url Is Nothing AndAlso value IsNot Nothing AndAlso value.StartsWith("http") Then
-                    _Url = value
-                End If
-            End Set
-        End Property
-        Private _Url As String = Nothing
-
-        ''' <summary>
-        ''' Mod 的作者列表。
-        ''' </summary>
-        Public Property Authors As String
-            Get
-                If _Authors Is Nothing Then Load()
-                Return _Authors
-            End Get
-            Set(value As String)
-                If _Authors Is Nothing AndAlso Not String.IsNullOrWhiteSpace(value) Then
-                    _Authors = value
-                End If
-            End Set
-        End Property
-        Private _Authors As String = Nothing
-
-        ''' <summary>
-        ''' 依赖项,其中包括了 Minecraft 的版本要求。格式为 ModID - VersionRequirement,若无版本要求则为 Nothing。
-        ''' </summary>
-        Public ReadOnly Property Dependencies As Dictionary(Of String, String)
-            Get
-                Load()
-                Return _Dependencies
-            End Get
-        End Property
-        Private _Dependencies As New Dictionary(Of String, String)
-        Private Sub AddDependency(ModID As String, Optional VersionRequirement As String = Nothing)
-            '确保信息正确
-            If ModID Is Nothing OrElse ModID.Count < 2 Then Exit Sub
-            ModID = ModID.ToLower
-            If ModID = "name" OrElse Val(ModID).ToString = ModID Then Exit Sub '跳过 name 与纯数字 id
-            If VersionRequirement Is Nothing OrElse ((Not VersionRequirement.Contains(".")) AndAlso (Not VersionRequirement.Contains("-"))) OrElse VersionRequirement.Contains("$") Then
-                VersionRequirement = Nothing
-            Else
-                If (Not VersionRequirement.StartsWith("[")) AndAlso (Not VersionRequirement.StartsWith("(")) AndAlso (Not VersionRequirement.EndsWith("]")) AndAlso (Not VersionRequirement.EndsWith(")")) Then VersionRequirement = "[" & VersionRequirement & ",)"
-            End If
-            '向依赖项中添加
-            If _Dependencies.ContainsKey(ModID) Then
-                If _Dependencies(ModID) Is Nothing Then _Dependencies(ModID) = VersionRequirement
-            Else
-                _Dependencies.Add(ModID, VersionRequirement)
-            End If
-        End Sub
-
-#End Region
-
-#Region "加载步骤标记"
-
-        '1. 进行文件可用性检查
-        '   成功:继续第二步。
-        '   失败:标记 FileUnavailableReason, 并停止后续加载。
-        ''' <summary>
-        ''' 是否已进行 Mod 文件的基础加载。(这包括第一步和第二步)
-        ''' </summary>
-        Private IsLoaded As Boolean = False
-        ''' <summary>
-        ''' Mod 文件是否可被正常读取。
-        ''' </summary>
-        Public ReadOnly Property IsFileAvailable As Boolean
-            Get
-                Load()
-                Return FileUnavailableReason Is Nothing
-            End Get
-        End Property
-        ''' <summary>
-        ''' Mod 文件出错的原因。若无错误,则为 Nothing。
-        ''' </summary>
-        Public ReadOnly Property FileUnavailableReason As Exception
-            Get
-                Load()
-                Return _FileUnavailableReason
-            End Get
-        End Property
-        Private _FileUnavailableReason As Exception = Nothing
-
-        '2. 进行 .class 以外的信息获取
-        '   成功:标记 IsInfoWithoutClassAvailable。
-        '   失败:什么也不干。如果需要补充信息的话,检测到 IsInfoWithoutClassAvailable 为 False,会自动继续加载。
-        ''' <summary>
-        ''' 是否已在不获取 .class 文件的前提下完成了所需信息的加载。
-        ''' </summary>
-        Private IsInfoWithoutClassAvailable As Boolean = False
-
-        '3. 尝试从 .class 文件中获取信息
-        '   成功:标记 IsInfoWithClassAvailable。
-        '   失败:什么也不干。
-        ''' <summary>
-        ''' 是否已进行 .class 文件的信息获取。
-        ''' </summary>
-        Private IsInfoWithClassLoaded As Boolean = False
-        ''' <summary>
-        ''' 是否已在 .class 文件中完成了所需信息的加载。
-        ''' </summary>
-        Private IsInfoWithClassAvailable As Boolean = False
-
-#End Region
-
-#Region "加载"
-
-        ''' <summary>
-        ''' 初始化所有数据。
-        ''' </summary>
-        Private Sub Init()
-            _Name = Nothing
-            _Description = Nothing
-            _Version = Nothing
-            _ModId = Nothing
-            PossibleModId = New List(Of String)
-            _Dependencies = New Dictionary(Of String, String)
-            IsLoaded = False
-            _FileUnavailableReason = Nothing
-            IsInfoWithoutClassAvailable = False
-            IsInfoWithClassLoaded = False
-            IsInfoWithClassAvailable = False
-        End Sub
-
-        ''' <summary>
-        ''' 进行文件可用性检查与 .class 以外的信息获取。
-        ''' </summary>
-        Public Sub Load(Optional ForceReload As Boolean = False)
-            If IsLoaded AndAlso Not ForceReload Then Exit Sub
-            '初始化
-            Init()
-            '阶段 1:基础可用性检查
-            Dim Jar As ZipArchive
-            Try
-                '打开 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("文件内容为空")
-            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
-                If Jar IsNot Nothing Then Jar.Dispose()
-            Catch
-            End Try
-            '完成标记
-            IsLoaded = True
-        End Sub
-
-        ''' <summary>
-        ''' 进行不使用 .class 文件的信息获取。
-        ''' </summary>
-        Private Sub LoadWithoutClass(Jar As ZipArchive)
-
-#Region "尝试使用 mcmod.info"
-            Try
-                '获取信息文件
-                Dim InfoEntry As ZipArchiveEntry = Jar.GetEntry("mcmod.info")
-                Dim InfoString As String = Nothing
-                If InfoEntry IsNot Nothing Then
-                    InfoString = ReadFile(InfoEntry.Open())
-                    If InfoString.Length < 15 Then InfoString = Nothing
-                End If
-                If InfoString Is Nothing Then Exit Try
-                '获取可用 Json 项
-                Dim InfoObject As JObject
-                Dim JsonObject = GetJson(InfoString)
-                If JsonObject.Type = JTokenType.Array Then
-                    InfoObject = JsonObject(0)
-                Else
-                    InfoObject = JsonObject("modList")(0)
-                End If
-                '从文件中获取 Mod 信息项
-                Name = InfoObject("name")
-                Description = InfoObject("description")
-                Version = InfoObject("version")
-                Url = InfoObject("url")
-                ModId = InfoObject("modid")
-                Dim AuthorJson As JArray = InfoObject("authorList")
-                If AuthorJson IsNot Nothing Then
-                    Dim Author As New List(Of String)
-                    For Each Token In AuthorJson
-                        Author.Add(Token.ToString)
-                    Next
-                    If Author.Count > 0 Then Authors = Join(Author, ", ")
-                End If
-                Dim Reqs As JArray = InfoObject("requiredMods")
-                If Reqs IsNot Nothing Then
-                    For Each Token As String In Reqs
-                        If Not String.IsNullOrEmpty(Token) Then
-                            Token = Token.Substring(Token.IndexOf(":") + 1)
-                            If Token.Contains("@") Then
-                                AddDependency(Token.Split("@")(0), Token.Split("@")(1))
-                            Else
-                                AddDependency(Token)
-                            End If
-                        End If
-                    Next
-                End If
-                Reqs = InfoObject("dependancies")
-                If Reqs IsNot Nothing Then
-                    For Each Token As String In Reqs
-                        If Not String.IsNullOrEmpty(Token) Then
-                            Token = Token.Substring(Token.IndexOf(":") + 1)
-                            If Token.Contains("@") Then
-                                AddDependency(Token.Split("@")(0), Token.Split("@")(1))
-                            Else
-                                AddDependency(Token)
-                            End If
-                        End If
-                    Next
-                End If
-            Catch ex As Exception
-                Log(ex, "读取 mcmod.info 时出现未知错误(" & Path & ")", LogLevel.Developer)
-            End Try
-#End Region
-#Region "尝试使用 mods.toml"
-            Try
-                '获取 mods.toml 文件
-                Dim TomlEntry As ZipArchiveEntry = Jar.GetEntry("META-INF/mods.toml")
-                Dim TomlText As String = Nothing
-                If TomlEntry IsNot Nothing Then
-                    TomlText = ReadFile(TomlEntry.Open())
-                    If TomlText.Length < 15 Then TomlText = Nothing
-                End If
-                If TomlText Is Nothing Then Exit Try
-                '文件标准化:统一换行符为 vbLf,去除注释、头尾的空格、空行
-                Dim Lines As New List(Of String)
-                For Each Line In TomlText.Replace(vbCrLf, vbLf).Replace(vbCr, vbLf).Split(vbLf) '统一换行符
-                    If Line.StartsWith("#") Then '去除注释
-                        Continue For
-                    ElseIf Line.Contains("#") Then
-                        Line = Line.Substring(0, Line.IndexOf("#"))
-                    End If
-                    Line = Line.Trim(New Char() {" "c, "	"c, " "c}) '去除头尾的空格
-                    If Line.Count > 0 Then Lines.Add(Line) '去除空行
-                Next
-                '读取文件数据
-                Dim TomlData As New List(Of KeyValuePair(Of String, Dictionary(Of String, Object))) From {New KeyValuePair(Of String, Dictionary(Of String, Object))("", New Dictionary(Of String, Object))}
-                For i = 0 To Lines.Count - 1
-                    Dim Line As String = Lines(i)
-                    If Line.StartsWith("[[") AndAlso Line.EndsWith("]]") Then
-                        '段落标记
-                        Dim Header = Line.Replace("[", "").Replace("]", "")
-                        TomlData.Add(New KeyValuePair(Of String, Dictionary(Of String, Object))(Header, New Dictionary(Of String, Object)))
-                    ElseIf Line.Contains("=") Then
-                        '字段标记
-                        Dim Key As String = Line.Substring(0, Line.IndexOf("=")).TrimEnd(New Char() {" "c, "	"c, " "c})
-                        Dim RawValue As String = Line.Substring(Line.IndexOf("=") + 1).TrimStart(New Char() {" "c, "	"c, " "c})
-                        Dim Value As Object
-                        If RawValue.StartsWith("""") AndAlso RawValue.EndsWith("""") Then
-                            '单行字符串
-                            Value = RawValue.Trim("""")
-                        ElseIf RawValue.StartsWith("'''") Then
-                            '多行字符串
-                            Dim ValueLines As New List(Of String) From {RawValue.TrimStart("'")}
-                            Do Until i >= Lines.Count - 1
-                                i += 1
-                                Dim ValueLine As String = Lines(i)
-                                If ValueLine.EndsWith("'''") Then
-                                    ValueLines.Add(ValueLine.TrimEnd("'"))
-                                    Exit Do
-                                Else
-                                    ValueLines.Add(ValueLine)
-                                End If
-                            Loop
-                            Value = Join(ValueLines, vbLf).Trim(vbLf).Replace(vbLf, vbCrLf)
-                        ElseIf RawValue.ToLower = "true" OrElse RawValue.ToLower = "false" Then
-                            '布尔型
-                            Value = (RawValue.ToLower = "true")
-                        ElseIf Val(RawValue).ToString = RawValue Then
-                            '数字型
-                            Value = Val(RawValue)
-                        Else
-                            '不知道是个啥玩意儿,直接存储
-                            Value = RawValue
-                        End If
-                        TomlData.Last.Value(Key) = Value
-                    Else
-                        '不知道是个啥玩意儿
-                        Exit Try
-                    End If
-                Next
-                '从文件数据中获取信息
-                Dim ModEntry As Dictionary(Of String, Object) = Nothing
-                For Each TomlSubData In TomlData
-                    If TomlSubData.Key = "mods" Then
-                        ModEntry = TomlSubData.Value
-                        Exit For
-                    End If
-                Next
-                If ModEntry Is Nothing OrElse Not ModEntry.ContainsKey("modId") Then Exit Try
-                ModId = ModEntry("modId")
-                If _ModId Is Nothing Then Exit Try '设置了无效的 ModID
-                If ModEntry.ContainsKey("displayName") Then Name = ModEntry("displayName")
-                If ModEntry.ContainsKey("description") Then Description = ModEntry("description")
-                If ModEntry.ContainsKey("version") Then Version = ModEntry("version")
-                If TomlData(0).Value.ContainsKey("displayURL") Then Url = TomlData(0).Value("displayURL")
-                If TomlData(0).Value.ContainsKey("authors") Then Authors = TomlData(0).Value("authors")
-                For Each TomlSubData In TomlData
-                    If TomlSubData.Key.StartsWith("dependencies") Then
-                        Dim DepEntry As Dictionary(Of String, Object) = TomlSubData.Value
-                        If DepEntry.ContainsKey("modId") AndAlso DepEntry.ContainsKey("mandatory") AndAlso DepEntry("mandatory") AndAlso
-                           DepEntry.ContainsKey("side") AndAlso Not DepEntry("side").ToString.ToLower = "server" Then
-                            AddDependency(DepEntry("modId"), If(DepEntry.ContainsKey("versionRange"), DepEntry("versionRange"), Nothing))
-                        End If
-                    End If
-                Next
-                '加载成功
-                GoTo Finished
-            Catch ex As Exception
-                Log(ex, "读取 mods.toml 时出现未知错误(" & Path & ")", LogLevel.Developer)
-            End Try
-#End Region
-#Region "尝试使用 fabric.mod.json"
-            Try
-                '获取 fabric.mod.json 文件
-                Dim FabricEntry As ZipArchiveEntry = Jar.GetEntry("fabric.mod.json")
-                Dim FabricText As String = Nothing
-                If FabricEntry IsNot Nothing Then
-                    FabricText = ReadFile(FabricEntry.Open(), Encoding.UTF8)
-                    If Not FabricText.Contains("schemaVersion") Then FabricText = Nothing
-                End If
-                If FabricText Is Nothing Then Exit Try
-                Dim FabricObject As JObject = GetJson(FabricText)
-GotFabric:
-                '从文件中获取 Mod 信息项
-                If FabricObject.ContainsKey("name") Then Name = FabricObject("name")
-                If FabricObject.ContainsKey("version") Then Version = FabricObject("version")
-                If FabricObject.ContainsKey("description") Then Description = FabricObject("description")
-                If FabricObject.ContainsKey("id") Then ModId = FabricObject("id")
-                If FabricObject.ContainsKey("contact") Then Url = If(FabricObject("contact")("homepage"), "")
-                Dim AuthorJson As JArray = FabricObject("authors")
-                If AuthorJson IsNot Nothing Then
-                    Dim Author As New List(Of String)
-                    For Each Token In AuthorJson
-                        Author.Add(Token.ToString)
-                    Next
-                    If Author.Count > 0 Then Authors = Join(Author, ", ")
-                End If
-                'If (Not FabricObject.ContainsKey("serverSideOnly")) OrElse FabricObject("serverSideOnly")("value").ToObject(Of Boolean) = False Then
-                '    '添加 Minecraft 依赖
-                '    Dim DepMinecraft As String = If(If(FabricObject("acceptedMinecraftVersions") IsNot Nothing, FabricObject("acceptedMinecraftVersions")("value"), ""), "")
-                '    If DepMinecraft <> "" Then AddDependency("minecraft", DepMinecraft)
-                '    '添加其他依赖
-                '    Dim Deps As String = If(If(FabricObject("dependencies") IsNot Nothing, FabricObject("dependencies")("value"), ""), "")
-                '    If Deps <> "" Then
-                '        For Each Dep In Deps.Split(";")
-                '            If Dep = "" OrElse Not Dep.StartsWith("required-") Then Continue For
-                '            Dep = Dep.Substring(Dep.IndexOf(":") + 1)
-                '            If Dep.Contains("@") Then
-                '                AddDependency(Dep.Split("@")(0), Dep.Split("@")(1))
-                '            Else
-                '                AddDependency(Dep)
-                '            End If
-                '        Next
-                '    End If
-                'End If
-                '加载成功
-                GoTo Finished
-            Catch ex As Exception
-                Log(ex, "读取 fabric.mod.json 时出现未知错误(" & Path & ")", LogLevel.Developer)
-            End Try
-#End Region
-#Region "尝试使用 fml_cache_annotation.json"
-            Try
-                '获取 fml_cache_annotation.json 文件
-                Dim FmlEntry As ZipArchiveEntry = Jar.GetEntry("META-INF/fml_cache_annotation.json")
-                Dim FmlText As String = Nothing
-                If FmlEntry IsNot Nothing Then
-                    FmlText = ReadFile(FmlEntry.Open(), Encoding.UTF8)
-                    If Not FmlText.Contains("Lnet/minecraftforge/fml/common/Mod;") Then FmlText = Nothing
-                End If
-                If FmlText Is Nothing Then Exit Try
-                Dim FmlJson As JObject = GetJson(FmlText)
-                '获取可用 Json 项
-                Dim FmlObject As JObject = Nothing
-                For Each ModFilePair In FmlJson
-                    Dim ModFileAnnos As JArray = ModFilePair.Value("annotations")
-                    If ModFileAnnos IsNot Nothing Then
-                        '先获取 Mod
-                        For Each ModFileAnno In ModFileAnnos
-                            Dim Name As String = If(ModFileAnno("name"), "")
-                            If Name = "Lnet/minecraftforge/fml/common/Mod;" Then
-                                FmlObject = ModFileAnno("values")
-                                GoTo Got
-                            End If
-                        Next
-                    End If
-                Next
-                Exit Try
-Got:
-                '从文件中获取 Mod 信息项
-                If FmlObject.ContainsKey("useMetadata") AndAlso If(FmlObject("useMetadata")("value"), "").ToString.ToLower = "true" Then
-                    '要求使用 mcmod.info 中的信息
-                    Dim value As String = FmlObject("modid")("value")
-                    If value Is Nothing Then Exit Try
-                    value = RegexSeek(value.ToLower, "[0-9a-z_]+")
-                    If value IsNot Nothing AndAlso value.ToLower <> "name" AndAlso value.Count > 1 AndAlso Val(value).ToString <> value Then
-                        If Not PossibleModId.Contains(value) Then PossibleModId.Add(value)
-                    End If
-                    Exit Try
-                End If
-                If FmlObject.ContainsKey("name") Then Name = FmlObject("name")("value")
-                If FmlObject.ContainsKey("version") Then Version = FmlObject("version")("value")
-                If FmlObject.ContainsKey("modid") Then ModId = FmlObject("modid")("value")
-                If (Not FmlObject.ContainsKey("serverSideOnly")) OrElse FmlObject("serverSideOnly")("value").ToObject(Of Boolean) = False Then
-                    '添加 Minecraft 依赖
-                    Dim DepMinecraft As String = If(If(FmlObject("acceptedMinecraftVersions") IsNot Nothing, FmlObject("acceptedMinecraftVersions")("value"), ""), "")
-                    If DepMinecraft <> "" Then AddDependency("minecraft", DepMinecraft)
-                    '添加其他依赖
-                    Dim Deps As String = If(If(FmlObject("dependencies") IsNot Nothing, FmlObject("dependencies")("value"), ""), "")
-                    If Deps <> "" Then
-                        For Each Dep In Deps.Split(";")
-                            If Dep = "" OrElse Not Dep.StartsWith("required-") Then Continue For
-                            Dep = Dep.Substring(Dep.IndexOf(":") + 1)
-                            If Dep.Contains("@") Then
-                                AddDependency(Dep.Split("@")(0), Dep.Split("@")(1))
-                            Else
-                                AddDependency(Dep)
-                            End If
-                        Next
-                    End If
-                End If
-            Catch ex As Exception
-                Log(ex, "读取 fml_cache_annotation.json 时出现未知错误(" & Path & ")", LogLevel.Developer)
-            End Try
-#End Region
-Finished:
-#Region "将 Version 代号转换为 META-INF 中的版本"
-            If _Version = "version" Then
-                Try
-                    Dim MetaEntry As ZipArchiveEntry = Jar.GetEntry("META-INF/MANIFEST.MF")
-                    If MetaEntry IsNot Nothing Then
-                        Dim MetaString As String = ReadFile(MetaEntry.Open()).Replace(" :", ":").Replace(": ", ":")
-                        If MetaString.Contains("Implementation-Version:") Then
-                            MetaString = MetaString.Substring(MetaString.IndexOf("Implementation-Version:") + "Implementation-Version:".Count)
-                            MetaString = MetaString.Substring(0, MetaString.IndexOfAny(vbCrLf.ToCharArray)).Trim
-                            Version = MetaString
-                        End If
-                    End If
-                Catch ex As Exception
-                    Log("获取 META-INF 中的版本信息失败(" & Path & ")", LogLevel.Developer)
-                    Version = Nothing
-                End Try
-            End If
-            If _Version IsNot Nothing AndAlso Not (_Version.Contains(".") OrElse _Version.Contains("-")) Then Version = Nothing
-#End Region
-
-            IsInfoWithoutClassAvailable = _ModId IsNot Nothing AndAlso _Version IsNot Nothing
-        End Sub
-
-        ''' <summary>
-        ''' 进行使用 .class 文件的信息获取。
-        ''' </summary>
-        Private Sub LoadWithClass(Jar As ZipArchive)
-            Try
-                '查找入口点文件
-                Dim ModClass As String = Nothing
-                Dim ModClassNotBest As String = Nothing '非完美匹配
-                For Each Entry In Jar.Entries
-                    If Entry.Name.EndsWith(".class") Then
-                        Dim Temp As String = ReadFile(Entry.Open()).ToLower
-                        If Temp.Contains("#lnet/minecraftforge/fml/common/mod;") Then
-                            ModClass = Temp
-                            Exit For
-                        ElseIf Temp.Contains("modid") Then
-                            ModClassNotBest = Temp
-                        End If
-                    End If
-                Next
-                If ModClass Is Nothing Then ModClass = ModClassNotBest
-                If ModClass Is Nothing Then Throw New FileNotFoundException("未找到 Mod 入口点")
-                ModClass = ModClass.Replace("ljava/lang/string;", "")
-                If ModClass.Count > 3000 Then ModClass = ModClass.Substring(0, 3000) '如果文件过大,截取前 3000 Byte
-                Dim IndexHead As Integer, IndexTail As Integer, IncreaseCount As Integer
-                '获取 ModID
-                If _ModId Is Nothing Then
-                    IndexHead = ModClass.IndexOf("modid") + "modid".Length + 1
-                    IncreaseCount = 0
-                    Do While Convert.ToInt32(ModClass(IndexHead)) < 32
-                        IndexHead += 1
-                        IncreaseCount += 1
-                        If IncreaseCount > 10 Then Throw New Exception("ModID 头匹配失败")
-                    Loop
-                    IndexTail = IndexHead + 1
-                    IncreaseCount = 0
-                    Do Until Convert.ToInt32(ModClass(IndexTail)) < 32
-                        IndexTail += 1
-                        IncreaseCount += 1
-                        If IncreaseCount > 50 Then Throw New Exception("ModID 尾匹配失败")
-                    Loop
-                    ModId = ModClass.Substring(IndexHead, IndexTail - IndexHead)
-                End If
-                '获取 Version
-                If _Version Is Nothing AndAlso ModClass.Contains("version") Then
-                    IndexHead = ModClass.IndexOf("version") + "version".Length + 1
-                    IncreaseCount = 0
-                    Do While Convert.ToInt32(ModClass(IndexHead)) < 32
-                        IndexHead += 1
-                        IncreaseCount += 1
-                        If IncreaseCount > 10 Then GoTo VersionFindFail
-                    Loop
-                    IndexTail = IndexHead + 1
-                    IncreaseCount = 0
-                    Do While ModClass(IndexTail) = "."c OrElse ModClass(IndexTail) = "-"c OrElse
-                         (ModClass(IndexTail) >= "0"c AndAlso ModClass(IndexTail) <= "9"c) OrElse
-                         (ModClass(IndexTail) >= "a"c AndAlso ModClass(IndexTail) <= "z"c)
-                        IndexTail += 1
-                        IncreaseCount += 1
-                        If IncreaseCount > 50 Then GoTo VersionFindFail
-                    Loop
-                    _Version = ModClass.Substring(IndexHead, IndexTail - IndexHead)
-                End If
-VersionFindFail:
-                '获取 Dependencies
-                IndexHead = ModClass.IndexOf("dependencies")
-                If IndexHead > 0 Then
-                    If ModClass.Count >= IndexHead + 300 Then ModClass = ModClass.Substring(IndexHead, 299)
-                    Dim Deps As List(Of String) = RegexSearch(ModClass, "(?<=required-((before|after|before-client|after-client)?):)[0-9a-z]+(@[\(\[]{1}[0-9.,]+[\)\]]{1})?")
-                    For Each Token As String In Deps
-                        If Not String.IsNullOrEmpty(Token) Then
-                            If Token.Contains("@") Then
-                                AddDependency(Token.Split("@")(0), Token.Split("@")(1))
-                            Else
-                                AddDependency(Token)
-                            End If
-                        End If
-                    Next
-                End If
-            Catch ex As Exception
-                Log(ex, "Mod Class 信息不可用(" & If(Path, "null") & ")", LogLevel.Normal)
-            End Try
-            IsInfoWithClassAvailable = _ModId IsNot Nothing AndAlso _Version IsNot Nothing
-        End Sub
-
-#End Region
-
-        ''' <summary>
-        ''' 是否可能为前置 Mod。
-        ''' </summary>
-        Public Function IsPresetMod() As Boolean
-            Return Dependencies.Count = 0 AndAlso (Name IsNot Nothing) AndAlso (Name.ToLower.Contains("core") OrElse Name.ToLower.Contains("lib"))
-        End Function
-
-        ''' <summary>
-        ''' 根据完整文件路径的文件扩展名判断是否为 Mod 文件。
-        ''' </summary>
-        Public Shared Function IsModFile(Path As String)
-            If Path Is Nothing OrElse Not Path.Contains(".") Then Return False
-            Path = Path.ToLower
-            If Path.EndsWith(".jar") OrElse Path.EndsWith(".zip") OrElse Path.EndsWith(".litemod") OrElse
-               Path.EndsWith(".jar.disabled") OrElse Path.EndsWith(".zip.disabled") OrElse Path.EndsWith(".litemod.disabled") Then Return True
-            Return False
-        End Function
-
-    End Class
-
-    '加载 Mod 列表
-    Public McModLoader As New LoaderTask(Of String, List(Of McMod))("Mod List Loader", AddressOf McModLoad)
-    Private Sub McModLoad(Loader As LoaderTask(Of String, List(Of McMod)))
-        Try
-            RunInUiWait(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.ShowProgress = False)
-
-            '获取 Mod 文件夹下的可用文件列表
-            Dim ModFileList As New List(Of FileInfo)
-            If Directory.Exists(Loader.Input) Then
-                Dim RawName As String = Loader.Input.ToLower
-                For Each File As FileInfo In EnumerateFiles(Loader.Input)
-                    If File.DirectoryName.ToLower & "\" <> RawName Then
-                        '仅当 Forge 1.13- 且文件夹名与版本号相同时,才加载该子文件夹下的 Mod
-                        If Not (PageVersionLeft.Version IsNot Nothing AndAlso PageVersionLeft.Version.Version.HasForge AndAlso
-                                PageVersionLeft.Version.Version.McCodeMain < 13 AndAlso
-                                File.Directory.Name = "1." & PageVersionLeft.Version.Version.McCodeMain & "." & PageVersionLeft.Version.Version.McCodeSub) Then
-                            Continue For
-                        End If
-                    End If
-                    If McMod.IsModFile(File.FullName) Then ModFileList.Add(File)
-                Next
-            End If
-
-            '确定是否显示进度
-            Loader.Progress = 0.03
-            If ModFileList.Count > 100 Then RunInUi(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.ShowProgress = True)
-
-            '加载为 Mod 列表
-            Dim ModList 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
-            Next
-            Loader.Progress = 0.93
-
-            '排序
-            ModList = Sort(ModList, Function(Left As McMod, Right As McMod) As Boolean
-                                        If (Left.State = McMod.McModState.Unavaliable) <> (Right.State = McMod.McModState.Unavaliable) Then
-                                            Return Left.State = McMod.McModState.Unavaliable
-                                        Else
-                                            Return Left.FileName < Right.FileName
-                                        End If
-                                    End Function)
-            Loader.Progress = 0.98
-
-            '回设
-            If Loader.IsAborted Then Exit Sub
-            Loader.Output = ModList
-
-        Catch ex As Exception
-            Log(ex, "Mod 列表加载失败", LogLevel.Debug)
-            Throw
-        End Try
-    End Sub
-
-#If DEBUG Then
-    ''' <summary>
-    ''' 检查 Mod 列表中存在的错误,返回错误信息的集合。
-    ''' </summary>
-    Public Function McModCheck(Version As McVersion, Mods As List(Of McMod)) As List(Of String)
-        Dim Result As New List(Of String)
-        '令所有 Mod 进行基础检查,并归纳需要检查的 Mod
-        Dim CurrentModList As New List(Of McMod)
-        For Each ModEntity In Mods
-            If Not ModEntity.IsFileAvailable Then
-                Result.Add("无法读取的 Mod 文件。" & vbCrLf & " - " & ModEntity.Path)
-                Continue For
-            End If
-            If ModEntity.State = McMod.McModState.Fine AndAlso ModEntity.ModId IsNot Nothing Then CurrentModList.Add(ModEntity)
-        Next
-        '添加默认依赖
-        Dim CurrentDependencies As New Dictionary(Of String, String()) '{DependencyVersion, Path}
-        If Version.State = McVersionState.Forge Then CurrentDependencies.Add("forge", {Version.Version.ForgeVersion, "Forge"})
-        CurrentDependencies.Add("minecraft", {Version.Version.McName, "Minecraft"})
-        '检查重复的 Mod,并添加对应的依赖
-        For Each ModEntity In CurrentModList
-            For Each PossibleModId In ModEntity.PossibleModId
-                If CurrentDependencies.ContainsKey(PossibleModId) Then
-                    If CurrentDependencies(PossibleModId)(2) = 1 Then
-                        Result.Add("重复添加了相同的 Mod,请尝试删除其中一个(ModID:" & PossibleModId & ")。" & vbCrLf &
-                                       " - " & ModEntity.FileName & vbCrLf &
-                                       " - " & CurrentDependencies(PossibleModId)(1))
-                    Else
-                        Log("[Minecraft] 由于可能有多个 ModID,跳过疑似的重复项(ModID:" & PossibleModId & ")。" & vbCrLf &
-                                       " - " & ModEntity.FileName & vbCrLf &
-                                       " - " & CurrentDependencies(PossibleModId)(1), LogLevel.Developer)
-                    End If
-                Else
-                    CurrentDependencies.Add(PossibleModId, {ModEntity.Version, ModEntity.FileName, ModEntity.PossibleModId.Count})
-                End If
-            Next
-        Next
-        '检查依赖
-        For Each ModEntity In CurrentModList
-            Try
-                For Each Dependency In ModEntity.Dependencies
-                    Dim ReqId As String = Dependency.Key
-                    If ReqId.Count < 2 Then Continue For '确保正常
-                    If ReqId = ModEntity.ModId Then Continue For '跳过自体引用
-                    If ReqId = "forgemultipartcbe" Then Continue For '跳过莫名其妙的引用
-                    If Dependency.Value IsNot Nothing Then
-                        '获取分段后的详细版本信息
-                        Dim ReqVersion As String = Dependency.Value
-                        Dim ReqVersionHeadCanEqual As Boolean = ReqVersion.StartsWith("[")
-                        Dim ReqVersionTailCanEqual As Boolean = ReqVersion.EndsWith("]")
-                        Dim ReqVersionHead As String
-                        Dim ReqVersionTail As String
-                        If ReqVersion.Contains(",") Then
-                            ReqVersionHead = ReqVersion.Split(",")(0).Trim("([ ".ToCharArray())
-                            ReqVersionTail = ReqVersion.Split(",")(1).Trim("]) ".ToCharArray())
-                        Else
-                            ReqVersionHead = ReqVersion.Trim("([]) ".ToCharArray())
-                            ReqVersionTail = ReqVersionHead
-                            If ReqId = "minecraft" AndAlso ReqVersionHead.Split(".").Count = 2 Then
-                                ReqVersionTail = ReqVersionHead.Split(".")(0) & "." & (Val(ReqVersionHead.Split(".")(1)) + 1)
-                                ReqVersionTailCanEqual = False
-                            End If
-                        End If
-                        If ReqVersionHead.StartsWith("1.") AndAlso ReqVersionHead.Contains("-") Then ReqVersionHead = ReqVersionHead.Substring(ReqVersionHead.LastIndexOf("-") + 1)
-                        If ReqVersionTail.StartsWith("1.") AndAlso ReqVersionTail.Contains("-") Then ReqVersionTail = ReqVersionTail.Substring(ReqVersionTail.LastIndexOf("-") + 1)
-                        '获取报错描述文本
-                        Dim VersionRequire As String
-                        If ReqVersionHead = ReqVersionTail Then
-                            VersionRequire = "应为 " & ReqVersionHead
-                        ElseIf ReqVersionHead.Contains(".") AndAlso ReqVersionTail.Contains(".") Then
-                            VersionRequire = "应为 " & ReqVersionHead & " 至 " & ReqVersionTail
-                        ElseIf ReqVersionHead.Contains(".") Then
-                            If ReqVersionHeadCanEqual Then
-                                VersionRequire = "最低应为 " & ReqVersionHead
-                            Else
-                                VersionRequire = "应高于 " & ReqVersionHead
-                            End If
-                        ElseIf ReqVersionTail.Contains(".") Then
-                            If ReqVersionTailCanEqual Then
-                                VersionRequire = "最高应为 " & ReqVersionHead
-                            Else
-                                VersionRequire = "应低于 " & ReqVersionHead
-                            End If
-                        Else
-                            VersionRequire = ""
-                        End If
-                        '检查前置 Mod 是否存在,并获取其版本
-                        If Not CurrentDependencies.ContainsKey(ReqId) Then
-                            Result.Add("缺少前置 Mod:" & ReqId & If(VersionRequire = "", "", ",其版本" & VersionRequire) & "。" & vbCrLf & " - " & ModEntity.FileName)
-                            Continue For
-                        End If
-                        Dim CurrentVersion As String = If(CurrentDependencies(ReqId)(0), "0.0")
-                        If CurrentVersion.StartsWith("1.") AndAlso CurrentVersion.Contains("-") Then CurrentVersion = CurrentVersion.Substring(CurrentVersion.LastIndexOf("-") + 1)
-                        '对比前置 Mod 头部版本
-                        If ReqVersionHead.Contains(".") Then
-                            If VersionSortInteger(ReqVersionHead, CurrentVersion) > If(ReqVersionHeadCanEqual, 0, -1) Then
-                                Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过低,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf &
-                                           " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", vbCrLf & " - 前置:" & CurrentDependencies(ReqId)(1), ""))
-                                Continue For
-                            End If
-                        End If
-                        '对比前置 Mod 尾部版本
-                        If ReqVersionTail.Contains(".") Then
-                            If VersionSortInteger(CurrentVersion, ReqVersionTail) > If(ReqVersionTailCanEqual, 0, -1) Then
-                                Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过高,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf &
-                                           " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", vbCrLf & " - 前置:" & CurrentDependencies(ReqId)(1), ""))
-                                Continue For
-                            End If
-                        End If
-                    Else
-                        If Not CurrentDependencies.ContainsKey(Dependency.Key) Then
-                            Result.Add("缺少前置 Mod:" & Dependency.Key & "。" & vbCrLf & " - " & ModEntity.FileName)
-                            Continue For
-                        End If
-                    End If
-                Next
-            Catch ex As Exception
-                Result.Add("检查 Mod 时出错:" & GetExceptionSummary(ex) & vbCrLf & " - " & ModEntity.FileName)
-                Log(ex, "检查 Mod 时出错")
-            End Try
-        Next
-        If Result.Count = 0 Then
-            Log("[Minecraft] Mod 检查未发现异常")
-        Else
-            Log("[Minecraft] Mod 检查异常结果:" & vbCrLf & Join(Result, vbCrLf))
-        End If
-        Return Result
-    End Function
-#End If
-
 #End Region
 
     ''' <summary>

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

@@ -0,0 +1,903 @@
+Imports System.IO.Compression
+
+Public Module ModMod
+
+    Public Class McMod
+
+#Region "基础"
+
+        ''' <summary>
+        ''' Mod 文件的地址。
+        ''' </summary>
+        Public ReadOnly Path As String
+        Public Sub New(Path As String)
+            Me.Path = If(Path, "")
+        End Sub
+
+        ''' <summary>
+        ''' Mod 的完整文件名。
+        ''' </summary>
+        Public ReadOnly Property FileName As String
+            Get
+                Return GetFileNameFromPath(Path)
+            End Get
+        End Property
+
+        ''' <summary>
+        ''' Mod 的完整文件名,去除最后的 .disabled。
+        ''' </summary>
+        Public ReadOnly Property RawFileName As String
+            Get
+                Return FileName.Replace(".disabled", "")
+            End Get
+        End Property
+
+        ''' <summary>
+        ''' Mod 的状态。
+        ''' </summary>
+        Public ReadOnly Property State As McModState
+            Get
+                Load()
+                If Not IsFileAvailable Then
+                    Return McModState.Unavaliable
+                ElseIf Path.EndsWith(".disabled") Then
+                    Return McModState.Disabled
+                Else
+                    Return McModState.Fine
+                End If
+            End Get
+        End Property
+        Public Enum McModState As Integer
+            Fine = 0
+            Disabled = 1
+            Unavaliable = 2
+        End Enum
+
+#End Region
+
+#Region "信息项"
+
+        ''' <summary>
+        ''' Mod 的名称。若不可用则为 ModID 或无扩展的文件名。
+        ''' </summary>
+        Public Property Name As String
+            Get
+                If _Name Is Nothing Then Load()
+                If _Name Is Nothing Then _Name = _ModId
+                If _Name Is Nothing Then _Name = GetFileNameWithoutExtentionFromPath(Path)
+                Return _Name
+            End Get
+            Set(value As String)
+                If _Name Is Nothing AndAlso value IsNot Nothing AndAlso
+                   value.ToLower <> "name" AndAlso value.Count > 1 AndAlso Val(value).ToString <> value Then
+                    _Name = value
+                End If
+            End Set
+        End Property
+        Private _Name As String = Nothing
+
+        ''' <summary>
+        ''' Mod 的描述信息。
+        ''' </summary>
+        Public Property Description As String
+            Get
+                If _Description Is Nothing Then Load()
+                If _Description Is Nothing AndAlso FileUnavailableReason IsNot Nothing Then _Description = FileUnavailableReason.Message
+                'If _Description Is Nothing Then _Description = Path
+                Return _Description
+            End Get
+            Set(value As String)
+                If _Description Is Nothing AndAlso value IsNot Nothing AndAlso value.Count > 2 Then
+                    _Description = value.ToString.Trim(vbLf)
+                    '优化显示:若以 [a-zA-Z0-9] 结尾,加上小数点句号
+                    If _Description.ToLower.LastIndexOfAny("qwertyuiopasdfghjklzxcvbnm0123456789") = _Description.Count - 1 Then _Description += "."
+                End If
+            End Set
+        End Property
+        Private _Description As String = Nothing
+
+        ''' <summary>
+        ''' Mod 的版本,不保证符合版本格式规范。
+        ''' </summary>
+        Public Property Version As String
+            Get
+                If _Version Is Nothing Then Load()
+                Return _Version
+            End Get
+            Set(value As String)
+                If _Version IsNot Nothing AndAlso (_Version.Contains(".") OrElse _Version.Contains("-")) Then Exit Property
+                If value IsNot Nothing AndAlso value.ToLower.Contains("version") Then value = "version" '需要修改的标识
+                _Version = value
+            End Set
+        End Property
+        Private _Version As String = Nothing
+
+        ''' <summary>
+        ''' 用于依赖检查的 ModID。
+        ''' </summary>
+        Public Property ModId As String
+            Get
+                If _ModId Is Nothing Then Load()
+                Return _ModId
+            End Get
+            Set(value As String)
+                If value Is Nothing Then Exit Property
+                value = RegexSeek(value.ToLower, "[0-9a-z_-]+")
+                If value IsNot Nothing AndAlso value.ToLower <> "name" AndAlso value.Count > 1 AndAlso Val(value).ToString <> value Then
+                    If Not PossibleModId.Contains(value) Then PossibleModId.Add(value)
+                    If _ModId Is Nothing Then _ModId = value
+                End If
+            End Set
+        End Property
+        Private _ModId As String = Nothing
+        ''' <summary>
+        ''' 其他可能的 ModID。
+        ''' </summary>
+        Public PossibleModId As New List(Of String)
+
+        ''' <summary>
+        ''' Mod 的主页。
+        ''' </summary>
+        Public Property Url As String
+            Get
+                If _Url Is Nothing Then Load()
+                Return _Url
+            End Get
+            Set(value As String)
+                If _Url Is Nothing AndAlso value IsNot Nothing AndAlso value.StartsWith("http") Then
+                    _Url = value
+                End If
+            End Set
+        End Property
+        Private _Url As String = Nothing
+
+        ''' <summary>
+        ''' Mod 的作者列表。
+        ''' </summary>
+        Public Property Authors As String
+            Get
+                If _Authors Is Nothing Then Load()
+                Return _Authors
+            End Get
+            Set(value As String)
+                If _Authors Is Nothing AndAlso Not String.IsNullOrWhiteSpace(value) Then
+                    _Authors = value
+                End If
+            End Set
+        End Property
+        Private _Authors As String = Nothing
+
+        ''' <summary>
+        ''' 依赖项,其中包括了 Minecraft 的版本要求。格式为 ModID - VersionRequirement,若无版本要求则为 Nothing。
+        ''' </summary>
+        Public ReadOnly Property Dependencies As Dictionary(Of String, String)
+            Get
+                Load()
+                Return _Dependencies
+            End Get
+        End Property
+        Private _Dependencies As New Dictionary(Of String, String)
+        Private Sub AddDependency(ModID As String, Optional VersionRequirement As String = Nothing)
+            '确保信息正确
+            If ModID Is Nothing OrElse ModID.Count < 2 Then Exit Sub
+            ModID = ModID.ToLower
+            If ModID = "name" OrElse Val(ModID).ToString = ModID Then Exit Sub '跳过 name 与纯数字 id
+            If VersionRequirement Is Nothing OrElse ((Not VersionRequirement.Contains(".")) AndAlso (Not VersionRequirement.Contains("-"))) OrElse VersionRequirement.Contains("$") Then
+                VersionRequirement = Nothing
+            Else
+                If (Not VersionRequirement.StartsWith("[")) AndAlso (Not VersionRequirement.StartsWith("(")) AndAlso (Not VersionRequirement.EndsWith("]")) AndAlso (Not VersionRequirement.EndsWith(")")) Then VersionRequirement = "[" & VersionRequirement & ",)"
+            End If
+            '向依赖项中添加
+            If _Dependencies.ContainsKey(ModID) Then
+                If _Dependencies(ModID) Is Nothing Then _Dependencies(ModID) = VersionRequirement
+            Else
+                _Dependencies.Add(ModID, VersionRequirement)
+            End If
+        End Sub
+
+#End Region
+
+#Region "加载步骤标记"
+
+        '1. 进行文件可用性检查
+        '   成功:继续第二步。
+        '   失败:标记 FileUnavailableReason, 并停止后续加载。
+        ''' <summary>
+        ''' 是否已进行 Mod 文件的基础加载。(这包括第一步和第二步)
+        ''' </summary>
+        Private IsLoaded As Boolean = False
+        ''' <summary>
+        ''' Mod 文件是否可被正常读取。
+        ''' </summary>
+        Public ReadOnly Property IsFileAvailable As Boolean
+            Get
+                Load()
+                Return FileUnavailableReason Is Nothing
+            End Get
+        End Property
+        ''' <summary>
+        ''' Mod 文件出错的原因。若无错误,则为 Nothing。
+        ''' </summary>
+        Public ReadOnly Property FileUnavailableReason As Exception
+            Get
+                Load()
+                Return _FileUnavailableReason
+            End Get
+        End Property
+        Private _FileUnavailableReason As Exception = Nothing
+
+        '2. 进行 .class 以外的信息获取
+        '   成功:标记 IsInfoWithoutClassAvailable。
+        '   失败:什么也不干。如果需要补充信息的话,检测到 IsInfoWithoutClassAvailable 为 False,会自动继续加载。
+        ''' <summary>
+        ''' 是否已在不获取 .class 文件的前提下完成了所需信息的加载。
+        ''' </summary>
+        Private IsInfoWithoutClassAvailable As Boolean = False
+
+        '3. 尝试从 .class 文件中获取信息
+        '   成功:标记 IsInfoWithClassAvailable。
+        '   失败:什么也不干。
+        ''' <summary>
+        ''' 是否已进行 .class 文件的信息获取。
+        ''' </summary>
+        Private IsInfoWithClassLoaded As Boolean = False
+        ''' <summary>
+        ''' 是否已在 .class 文件中完成了所需信息的加载。
+        ''' </summary>
+        Private IsInfoWithClassAvailable As Boolean = False
+
+#End Region
+
+#Region "加载"
+
+        ''' <summary>
+        ''' 初始化所有数据。
+        ''' </summary>
+        Private Sub Init()
+            _Name = Nothing
+            _Description = Nothing
+            _Version = Nothing
+            _ModId = Nothing
+            PossibleModId = New List(Of String)
+            _Dependencies = New Dictionary(Of String, String)
+            IsLoaded = False
+            _FileUnavailableReason = Nothing
+            IsInfoWithoutClassAvailable = False
+            IsInfoWithClassLoaded = False
+            IsInfoWithClassAvailable = False
+        End Sub
+
+        ''' <summary>
+        ''' 进行文件可用性检查与 .class 以外的信息获取。
+        ''' </summary>
+        Public Sub Load(Optional ForceReload As Boolean = False)
+            If IsLoaded AndAlso Not ForceReload Then Exit Sub
+            '初始化
+            Init()
+            '阶段 1:基础可用性检查
+            Dim Jar As ZipArchive
+            Try
+                '打开 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("文件内容为空")
+            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
+                If Jar IsNot Nothing Then Jar.Dispose()
+            Catch
+            End Try
+            '完成标记
+            IsLoaded = True
+        End Sub
+
+        ''' <summary>
+        ''' 进行不使用 .class 文件的信息获取。
+        ''' </summary>
+        Private Sub LoadWithoutClass(Jar As ZipArchive)
+
+#Region "尝试使用 mcmod.info"
+            Try
+                '获取信息文件
+                Dim InfoEntry As ZipArchiveEntry = Jar.GetEntry("mcmod.info")
+                Dim InfoString As String = Nothing
+                If InfoEntry IsNot Nothing Then
+                    InfoString = ReadFile(InfoEntry.Open())
+                    If InfoString.Length < 15 Then InfoString = Nothing
+                End If
+                If InfoString Is Nothing Then Exit Try
+                '获取可用 Json 项
+                Dim InfoObject As JObject
+                Dim JsonObject = GetJson(InfoString)
+                If JsonObject.Type = JTokenType.Array Then
+                    InfoObject = JsonObject(0)
+                Else
+                    InfoObject = JsonObject("modList")(0)
+                End If
+                '从文件中获取 Mod 信息项
+                Name = InfoObject("name")
+                Description = InfoObject("description")
+                Version = InfoObject("version")
+                Url = InfoObject("url")
+                ModId = InfoObject("modid")
+                Dim AuthorJson As JArray = InfoObject("authorList")
+                If AuthorJson IsNot Nothing Then
+                    Dim Author As New List(Of String)
+                    For Each Token In AuthorJson
+                        Author.Add(Token.ToString)
+                    Next
+                    If Author.Count > 0 Then Authors = Join(Author, ", ")
+                End If
+                Dim Reqs As JArray = InfoObject("requiredMods")
+                If Reqs IsNot Nothing Then
+                    For Each Token As String In Reqs
+                        If Not String.IsNullOrEmpty(Token) Then
+                            Token = Token.Substring(Token.IndexOf(":") + 1)
+                            If Token.Contains("@") Then
+                                AddDependency(Token.Split("@")(0), Token.Split("@")(1))
+                            Else
+                                AddDependency(Token)
+                            End If
+                        End If
+                    Next
+                End If
+                Reqs = InfoObject("dependancies")
+                If Reqs IsNot Nothing Then
+                    For Each Token As String In Reqs
+                        If Not String.IsNullOrEmpty(Token) Then
+                            Token = Token.Substring(Token.IndexOf(":") + 1)
+                            If Token.Contains("@") Then
+                                AddDependency(Token.Split("@")(0), Token.Split("@")(1))
+                            Else
+                                AddDependency(Token)
+                            End If
+                        End If
+                    Next
+                End If
+            Catch ex As Exception
+                Log(ex, "读取 mcmod.info 时出现未知错误(" & Path & ")", LogLevel.Developer)
+            End Try
+#End Region
+#Region "尝试使用 mods.toml"
+            Try
+                '获取 mods.toml 文件
+                Dim TomlEntry As ZipArchiveEntry = Jar.GetEntry("META-INF/mods.toml")
+                Dim TomlText As String = Nothing
+                If TomlEntry IsNot Nothing Then
+                    TomlText = ReadFile(TomlEntry.Open())
+                    If TomlText.Length < 15 Then TomlText = Nothing
+                End If
+                If TomlText Is Nothing Then Exit Try
+                '文件标准化:统一换行符为 vbLf,去除注释、头尾的空格、空行
+                Dim Lines As New List(Of String)
+                For Each Line In TomlText.Replace(vbCrLf, vbLf).Replace(vbCr, vbLf).Split(vbLf) '统一换行符
+                    If Line.StartsWith("#") Then '去除注释
+                        Continue For
+                    ElseIf Line.Contains("#") Then
+                        Line = Line.Substring(0, Line.IndexOf("#"))
+                    End If
+                    Line = Line.Trim(New Char() {" "c, "	"c, " "c}) '去除头尾的空格
+                    If Line.Count > 0 Then Lines.Add(Line) '去除空行
+                Next
+                '读取文件数据
+                Dim TomlData As New List(Of KeyValuePair(Of String, Dictionary(Of String, Object))) From {New KeyValuePair(Of String, Dictionary(Of String, Object))("", New Dictionary(Of String, Object))}
+                For i = 0 To Lines.Count - 1
+                    Dim Line As String = Lines(i)
+                    If Line.StartsWith("[") AndAlso Line.EndsWith("]") Then
+                        '段落标记
+                        Dim Header = Line.Trim("[]".ToCharArray)
+                        TomlData.Add(New KeyValuePair(Of String, Dictionary(Of String, Object))(Header, New Dictionary(Of String, Object)))
+                    ElseIf Line.Contains("=") Then
+                        '字段标记
+                        Dim Key As String = Line.Substring(0, Line.IndexOf("=")).TrimEnd(New Char() {" "c, "	"c, " "c})
+                        Dim RawValue As String = Line.Substring(Line.IndexOf("=") + 1).TrimStart(New Char() {" "c, "	"c, " "c})
+                        Dim Value As Object
+                        If RawValue.StartsWith("""") AndAlso RawValue.EndsWith("""") Then
+                            '单行字符串
+                            Value = RawValue.Trim("""")
+                        ElseIf RawValue.StartsWith("'''") Then
+                            '多行字符串
+                            Dim ValueLines As New List(Of String) From {RawValue.TrimStart("'")}
+                            Do Until i >= Lines.Count - 1
+                                i += 1
+                                Dim ValueLine As String = Lines(i)
+                                If ValueLine.EndsWith("'''") Then
+                                    ValueLines.Add(ValueLine.TrimEnd("'"))
+                                    Exit Do
+                                Else
+                                    ValueLines.Add(ValueLine)
+                                End If
+                            Loop
+                            Value = Join(ValueLines, vbLf).Trim(vbLf).Replace(vbLf, vbCrLf)
+                        ElseIf RawValue.ToLower = "true" OrElse RawValue.ToLower = "false" Then
+                            '布尔型
+                            Value = (RawValue.ToLower = "true")
+                        ElseIf Val(RawValue).ToString = RawValue Then
+                            '数字型
+                            Value = Val(RawValue)
+                        Else
+                            '不知道是个啥玩意儿,直接存储
+                            Value = RawValue
+                        End If
+                        TomlData.Last.Value(Key) = Value
+                    Else
+                        '不知道是个啥玩意儿
+                        Exit Try
+                    End If
+                Next
+                '从文件数据中获取信息
+                Dim ModEntry As Dictionary(Of String, Object) = Nothing
+                For Each TomlSubData In TomlData
+                    If TomlSubData.Key = "mods" Then
+                        ModEntry = TomlSubData.Value
+                        Exit For
+                    End If
+                Next
+                If ModEntry Is Nothing OrElse Not ModEntry.ContainsKey("modId") Then Exit Try
+                ModId = ModEntry("modId")
+                If _ModId Is Nothing Then Exit Try '设置了无效的 ModID
+                If ModEntry.ContainsKey("displayName") Then Name = ModEntry("displayName")
+                If ModEntry.ContainsKey("description") Then Description = ModEntry("description")
+                If ModEntry.ContainsKey("version") Then Version = ModEntry("version")
+                If TomlData(0).Value.ContainsKey("displayURL") Then Url = TomlData(0).Value("displayURL")
+                If TomlData(0).Value.ContainsKey("authors") Then Authors = TomlData(0).Value("authors")
+                For Each TomlSubData In TomlData
+                    If TomlSubData.Key.ToLower = $"dependencies.{ModId.ToLower}" Then
+                        Dim DepEntry As Dictionary(Of String, Object) = TomlSubData.Value
+                        If DepEntry.ContainsKey("modId") AndAlso DepEntry.ContainsKey("mandatory") AndAlso DepEntry("mandatory") AndAlso
+                           DepEntry.ContainsKey("side") AndAlso Not DepEntry("side").ToString.ToLower = "server" Then
+                            AddDependency(DepEntry("modId"), If(DepEntry.ContainsKey("versionRange"), DepEntry("versionRange"), Nothing))
+                        End If
+                    End If
+                Next
+                '加载成功
+                GoTo Finished
+            Catch ex As Exception
+                Log(ex, "读取 mods.toml 时出现未知错误(" & Path & ")", LogLevel.Developer)
+            End Try
+#End Region
+#Region "尝试使用 fabric.mod.json"
+            Try
+                '获取 fabric.mod.json 文件
+                Dim FabricEntry As ZipArchiveEntry = Jar.GetEntry("fabric.mod.json")
+                Dim FabricText As String = Nothing
+                If FabricEntry IsNot Nothing Then
+                    FabricText = ReadFile(FabricEntry.Open(), Encoding.UTF8)
+                    If Not FabricText.Contains("schemaVersion") Then FabricText = Nothing
+                End If
+                If FabricText Is Nothing Then Exit Try
+                Dim FabricObject As JObject = GetJson(FabricText)
+GotFabric:
+                '从文件中获取 Mod 信息项
+                If FabricObject.ContainsKey("name") Then Name = FabricObject("name")
+                If FabricObject.ContainsKey("version") Then Version = FabricObject("version")
+                If FabricObject.ContainsKey("description") Then Description = FabricObject("description")
+                If FabricObject.ContainsKey("id") Then ModId = FabricObject("id")
+                If FabricObject.ContainsKey("contact") Then Url = If(FabricObject("contact")("homepage"), "")
+                Dim AuthorJson As JArray = FabricObject("authors")
+                If AuthorJson IsNot Nothing Then
+                    Dim Author As New List(Of String)
+                    For Each Token In AuthorJson
+                        Author.Add(Token.ToString)
+                    Next
+                    If Author.Count > 0 Then Authors = Join(Author, ", ")
+                End If
+                'If (Not FabricObject.ContainsKey("serverSideOnly")) OrElse FabricObject("serverSideOnly")("value").ToObject(Of Boolean) = False Then
+                '    '添加 Minecraft 依赖
+                '    Dim DepMinecraft As String = If(If(FabricObject("acceptedMinecraftVersions") IsNot Nothing, FabricObject("acceptedMinecraftVersions")("value"), ""), "")
+                '    If DepMinecraft <> "" Then AddDependency("minecraft", DepMinecraft)
+                '    '添加其他依赖
+                '    Dim Deps As String = If(If(FabricObject("dependencies") IsNot Nothing, FabricObject("dependencies")("value"), ""), "")
+                '    If Deps <> "" Then
+                '        For Each Dep In Deps.Split(";")
+                '            If Dep = "" OrElse Not Dep.StartsWith("required-") Then Continue For
+                '            Dep = Dep.Substring(Dep.IndexOf(":") + 1)
+                '            If Dep.Contains("@") Then
+                '                AddDependency(Dep.Split("@")(0), Dep.Split("@")(1))
+                '            Else
+                '                AddDependency(Dep)
+                '            End If
+                '        Next
+                '    End If
+                'End If
+                '加载成功
+                GoTo Finished
+            Catch ex As Exception
+                Log(ex, "读取 fabric.mod.json 时出现未知错误(" & Path & ")", LogLevel.Developer)
+            End Try
+#End Region
+#Region "尝试使用 fml_cache_annotation.json"
+            Try
+                '获取 fml_cache_annotation.json 文件
+                Dim FmlEntry As ZipArchiveEntry = Jar.GetEntry("META-INF/fml_cache_annotation.json")
+                Dim FmlText As String = Nothing
+                If FmlEntry IsNot Nothing Then
+                    FmlText = ReadFile(FmlEntry.Open(), Encoding.UTF8)
+                    If Not FmlText.Contains("Lnet/minecraftforge/fml/common/Mod;") Then FmlText = Nothing
+                End If
+                If FmlText Is Nothing Then Exit Try
+                Dim FmlJson As JObject = GetJson(FmlText)
+                '获取可用 Json 项
+                Dim FmlObject As JObject = Nothing
+                For Each ModFilePair In FmlJson
+                    Dim ModFileAnnos As JArray = ModFilePair.Value("annotations")
+                    If ModFileAnnos IsNot Nothing Then
+                        '先获取 Mod
+                        For Each ModFileAnno In ModFileAnnos
+                            Dim Name As String = If(ModFileAnno("name"), "")
+                            If Name = "Lnet/minecraftforge/fml/common/Mod;" Then
+                                FmlObject = ModFileAnno("values")
+                                GoTo Got
+                            End If
+                        Next
+                    End If
+                Next
+                Exit Try
+Got:
+                '从文件中获取 Mod 信息项
+                If FmlObject.ContainsKey("useMetadata") AndAlso If(FmlObject("useMetadata")("value"), "").ToString.ToLower = "true" Then
+                    '要求使用 mcmod.info 中的信息
+                    Dim value As String = FmlObject("modid")("value")
+                    If value Is Nothing Then Exit Try
+                    value = RegexSeek(value.ToLower, "[0-9a-z_]+")
+                    If value IsNot Nothing AndAlso value.ToLower <> "name" AndAlso value.Count > 1 AndAlso Val(value).ToString <> value Then
+                        If Not PossibleModId.Contains(value) Then PossibleModId.Add(value)
+                    End If
+                    Exit Try
+                End If
+                If FmlObject.ContainsKey("name") Then Name = FmlObject("name")("value")
+                If FmlObject.ContainsKey("version") Then Version = FmlObject("version")("value")
+                If FmlObject.ContainsKey("modid") Then ModId = FmlObject("modid")("value")
+                If (Not FmlObject.ContainsKey("serverSideOnly")) OrElse FmlObject("serverSideOnly")("value").ToObject(Of Boolean) = False Then
+                    '添加 Minecraft 依赖
+                    Dim DepMinecraft As String = If(If(FmlObject("acceptedMinecraftVersions") IsNot Nothing, FmlObject("acceptedMinecraftVersions")("value"), ""), "")
+                    If DepMinecraft <> "" Then AddDependency("minecraft", DepMinecraft)
+                    '添加其他依赖
+                    Dim Deps As String = If(If(FmlObject("dependencies") IsNot Nothing, FmlObject("dependencies")("value"), ""), "")
+                    If Deps <> "" Then
+                        For Each Dep In Deps.Split(";")
+                            If Dep = "" OrElse Not Dep.StartsWith("required-") Then Continue For
+                            Dep = Dep.Substring(Dep.IndexOf(":") + 1)
+                            If Dep.Contains("@") Then
+                                AddDependency(Dep.Split("@")(0), Dep.Split("@")(1))
+                            Else
+                                AddDependency(Dep)
+                            End If
+                        Next
+                    End If
+                End If
+            Catch ex As Exception
+                Log(ex, "读取 fml_cache_annotation.json 时出现未知错误(" & Path & ")", LogLevel.Developer)
+            End Try
+#End Region
+Finished:
+#Region "将 Version 代号转换为 META-INF 中的版本"
+            If _Version = "version" Then
+                Try
+                    Dim MetaEntry As ZipArchiveEntry = Jar.GetEntry("META-INF/MANIFEST.MF")
+                    If MetaEntry IsNot Nothing Then
+                        Dim MetaString As String = ReadFile(MetaEntry.Open()).Replace(" :", ":").Replace(": ", ":")
+                        If MetaString.Contains("Implementation-Version:") Then
+                            MetaString = MetaString.Substring(MetaString.IndexOf("Implementation-Version:") + "Implementation-Version:".Count)
+                            MetaString = MetaString.Substring(0, MetaString.IndexOfAny(vbCrLf.ToCharArray)).Trim
+                            Version = MetaString
+                        End If
+                    End If
+                Catch ex As Exception
+                    Log("获取 META-INF 中的版本信息失败(" & Path & ")", LogLevel.Developer)
+                    Version = Nothing
+                End Try
+            End If
+            If _Version IsNot Nothing AndAlso Not (_Version.Contains(".") OrElse _Version.Contains("-")) Then Version = Nothing
+#End Region
+
+            IsInfoWithoutClassAvailable = _ModId IsNot Nothing AndAlso _Version IsNot Nothing
+        End Sub
+
+        ''' <summary>
+        ''' 进行使用 .class 文件的信息获取。
+        ''' </summary>
+        Private Sub LoadWithClass(Jar As ZipArchive)
+            Try
+                '查找入口点文件
+                Dim ModClass As String = Nothing
+                Dim ModClassNotBest As String = Nothing '非完美匹配
+                For Each Entry In Jar.Entries
+                    If Entry.Name.EndsWith(".class") Then
+                        Dim Temp As String = ReadFile(Entry.Open()).ToLower
+                        If Temp.Contains("#lnet/minecraftforge/fml/common/mod;") Then
+                            ModClass = Temp
+                            Exit For
+                        ElseIf Temp.Contains("modid") Then
+                            ModClassNotBest = Temp
+                        End If
+                    End If
+                Next
+                If ModClass Is Nothing Then ModClass = ModClassNotBest
+                If ModClass Is Nothing Then Throw New FileNotFoundException("未找到 Mod 入口点")
+                ModClass = ModClass.Replace("ljava/lang/string;", "")
+                If ModClass.Count > 3000 Then ModClass = ModClass.Substring(0, 3000) '如果文件过大,截取前 3000 Byte
+                Dim IndexHead As Integer, IndexTail As Integer, IncreaseCount As Integer
+                '获取 ModID
+                If _ModId Is Nothing Then
+                    IndexHead = ModClass.IndexOf("modid") + "modid".Length + 1
+                    IncreaseCount = 0
+                    Do While Convert.ToInt32(ModClass(IndexHead)) < 32
+                        IndexHead += 1
+                        IncreaseCount += 1
+                        If IncreaseCount > 10 Then Throw New Exception("ModID 头匹配失败")
+                    Loop
+                    IndexTail = IndexHead + 1
+                    IncreaseCount = 0
+                    Do Until Convert.ToInt32(ModClass(IndexTail)) < 32
+                        IndexTail += 1
+                        IncreaseCount += 1
+                        If IncreaseCount > 50 Then Throw New Exception("ModID 尾匹配失败")
+                    Loop
+                    ModId = ModClass.Substring(IndexHead, IndexTail - IndexHead)
+                End If
+                '获取 Version
+                If _Version Is Nothing AndAlso ModClass.Contains("version") Then
+                    IndexHead = ModClass.IndexOf("version") + "version".Length + 1
+                    IncreaseCount = 0
+                    Do While Convert.ToInt32(ModClass(IndexHead)) < 32
+                        IndexHead += 1
+                        IncreaseCount += 1
+                        If IncreaseCount > 10 Then GoTo VersionFindFail
+                    Loop
+                    IndexTail = IndexHead + 1
+                    IncreaseCount = 0
+                    Do While ModClass(IndexTail) = "."c OrElse ModClass(IndexTail) = "-"c OrElse
+                         (ModClass(IndexTail) >= "0"c AndAlso ModClass(IndexTail) <= "9"c) OrElse
+                         (ModClass(IndexTail) >= "a"c AndAlso ModClass(IndexTail) <= "z"c)
+                        IndexTail += 1
+                        IncreaseCount += 1
+                        If IncreaseCount > 50 Then GoTo VersionFindFail
+                    Loop
+                    _Version = ModClass.Substring(IndexHead, IndexTail - IndexHead)
+                End If
+VersionFindFail:
+                '获取 Dependencies
+                IndexHead = ModClass.IndexOf("dependencies")
+                If IndexHead > 0 Then
+                    If ModClass.Count >= IndexHead + 300 Then ModClass = ModClass.Substring(IndexHead, 299)
+                    Dim Deps As List(Of String) = RegexSearch(ModClass, "(?<=required-((before|after|before-client|after-client)?):)[0-9a-z]+(@[\(\[]{1}[0-9.,]+[\)\]]{1})?")
+                    For Each Token As String In Deps
+                        If Not String.IsNullOrEmpty(Token) Then
+                            If Token.Contains("@") Then
+                                AddDependency(Token.Split("@")(0), Token.Split("@")(1))
+                            Else
+                                AddDependency(Token)
+                            End If
+                        End If
+                    Next
+                End If
+            Catch ex As Exception
+                Log(ex, "Mod Class 信息不可用(" & If(Path, "null") & ")", LogLevel.Normal)
+            End Try
+            IsInfoWithClassAvailable = _ModId IsNot Nothing AndAlso _Version IsNot Nothing
+        End Sub
+
+#End Region
+
+        ''' <summary>
+        ''' 是否可能为前置 Mod。
+        ''' </summary>
+        Public Function IsPresetMod() As Boolean
+            Return Dependencies.Count = 0 AndAlso (Name IsNot Nothing) AndAlso (Name.ToLower.Contains("core") OrElse Name.ToLower.Contains("lib"))
+        End Function
+
+        ''' <summary>
+        ''' 根据完整文件路径的文件扩展名判断是否为 Mod 文件。
+        ''' </summary>
+        Public Shared Function IsModFile(Path As String)
+            If Path Is Nothing OrElse Not Path.Contains(".") Then Return False
+            Path = Path.ToLower
+            If Path.EndsWith(".jar") OrElse Path.EndsWith(".zip") OrElse Path.EndsWith(".litemod") OrElse
+               Path.EndsWith(".jar.disabled") OrElse Path.EndsWith(".zip.disabled") OrElse Path.EndsWith(".litemod.disabled") Then Return True
+            Return False
+        End Function
+
+    End Class
+
+    '加载 Mod 列表
+    Public McModLoader As New LoaderTask(Of String, List(Of McMod))("Mod List Loader", AddressOf McModLoad)
+    Private Sub McModLoad(Loader As LoaderTask(Of String, List(Of McMod)))
+        Try
+            RunInUiWait(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.ShowProgress = False)
+
+            '获取 Mod 文件夹下的可用文件列表
+            Dim ModFileList As New List(Of FileInfo)
+            If Directory.Exists(Loader.Input) Then
+                Dim RawName As String = Loader.Input.ToLower
+                For Each File As FileInfo In EnumerateFiles(Loader.Input)
+                    If File.DirectoryName.ToLower & "\" <> RawName Then
+                        '仅当 Forge 1.13- 且文件夹名与版本号相同时,才加载该子文件夹下的 Mod
+                        If Not (PageVersionLeft.Version IsNot Nothing AndAlso PageVersionLeft.Version.Version.HasForge AndAlso
+                                PageVersionLeft.Version.Version.McCodeMain < 13 AndAlso
+                                File.Directory.Name = "1." & PageVersionLeft.Version.Version.McCodeMain & "." & PageVersionLeft.Version.Version.McCodeSub) Then
+                            Continue For
+                        End If
+                    End If
+                    If McMod.IsModFile(File.FullName) Then ModFileList.Add(File)
+                Next
+            End If
+
+            '确定是否显示进度
+            Loader.Progress = 0.03
+            If ModFileList.Count > 100 Then RunInUi(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.ShowProgress = True)
+
+            '加载为 Mod 列表
+            Dim ModList 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
+            Next
+            Loader.Progress = 0.93
+
+            '排序
+            ModList = Sort(ModList, Function(Left As McMod, Right As McMod) As Boolean
+                                        If (Left.State = McMod.McModState.Unavaliable) <> (Right.State = McMod.McModState.Unavaliable) Then
+                                            Return Left.State = McMod.McModState.Unavaliable
+                                        Else
+                                            Return Not Right.FileName.CompareTo(Left.FileName)
+                                        End If
+                                    End Function)
+            Loader.Progress = 0.98
+
+            '回设
+            If Loader.IsAborted Then Exit Sub
+            Loader.Output = ModList
+
+        Catch ex As Exception
+            Log(ex, "Mod 列表加载失败", LogLevel.Debug)
+            Throw
+        End Try
+    End Sub
+
+#If DEBUG Then
+    ''' <summary>
+    ''' 检查 Mod 列表中存在的错误,返回错误信息的集合。
+    ''' </summary>
+    Public Function McModCheck(Version As McVersion, Mods As List(Of McMod)) As List(Of String)
+        Dim Result As New List(Of String)
+        '令所有 Mod 进行基础检查,并归纳需要检查的 Mod
+        Dim CurrentModList As New List(Of McMod)
+        For Each ModEntity In Mods
+            If Not ModEntity.IsFileAvailable Then
+                Result.Add("无法读取的 Mod 文件。" & vbCrLf & " - " & ModEntity.Path)
+                Continue For
+            End If
+            If ModEntity.State = McMod.McModState.Fine AndAlso ModEntity.ModId IsNot Nothing Then CurrentModList.Add(ModEntity)
+        Next
+        '添加默认依赖
+        Dim CurrentDependencies As New Dictionary(Of String, String()) '{DependencyVersion, Path}
+        If Version.State = McVersionState.Forge Then CurrentDependencies.Add("forge", {Version.Version.ForgeVersion, "Forge"})
+        CurrentDependencies.Add("minecraft", {Version.Version.McName, "Minecraft"})
+        '检查重复的 Mod,并添加对应的依赖
+        For Each ModEntity In CurrentModList
+            For Each PossibleModId In ModEntity.PossibleModId
+                If CurrentDependencies.ContainsKey(PossibleModId) Then
+                    If CurrentDependencies(PossibleModId)(2) = 1 Then
+                        Result.Add("重复添加了相同的 Mod,请尝试删除其中一个(ModID:" & PossibleModId & ")。" & vbCrLf &
+                                       " - " & ModEntity.FileName & vbCrLf &
+                                       " - " & CurrentDependencies(PossibleModId)(1))
+                    Else
+                        Log("[Minecraft] 由于可能有多个 ModID,跳过疑似的重复项(ModID:" & PossibleModId & ")。" & vbCrLf &
+                                       " - " & ModEntity.FileName & vbCrLf &
+                                       " - " & CurrentDependencies(PossibleModId)(1), LogLevel.Developer)
+                    End If
+                Else
+                    CurrentDependencies.Add(PossibleModId, {ModEntity.Version, ModEntity.FileName, ModEntity.PossibleModId.Count})
+                End If
+            Next
+        Next
+        '检查依赖
+        For Each ModEntity In CurrentModList
+            Try
+                For Each Dependency In ModEntity.Dependencies
+                    Dim ReqId As String = Dependency.Key
+                    If ReqId.Count < 2 Then Continue For '确保正常
+                    If ReqId = ModEntity.ModId Then Continue For '跳过自体引用
+                    If ReqId = "forgemultipartcbe" Then Continue For '跳过莫名其妙的引用
+                    If Dependency.Value IsNot Nothing Then
+                        '获取分段后的详细版本信息
+                        Dim ReqVersion As String = Dependency.Value
+                        Dim ReqVersionHeadCanEqual As Boolean = ReqVersion.StartsWith("[")
+                        Dim ReqVersionTailCanEqual As Boolean = ReqVersion.EndsWith("]")
+                        Dim ReqVersionHead As String
+                        Dim ReqVersionTail As String
+                        If ReqVersion.Contains(",") Then
+                            ReqVersionHead = ReqVersion.Split(",")(0).Trim("([ ".ToCharArray())
+                            ReqVersionTail = ReqVersion.Split(",")(1).Trim("]) ".ToCharArray())
+                        Else
+                            ReqVersionHead = ReqVersion.Trim("([]) ".ToCharArray())
+                            ReqVersionTail = ReqVersionHead
+                            If ReqId = "minecraft" AndAlso ReqVersionHead.Split(".").Count = 2 Then
+                                ReqVersionTail = ReqVersionHead.Split(".")(0) & "." & (Val(ReqVersionHead.Split(".")(1)) + 1)
+                                ReqVersionTailCanEqual = False
+                            End If
+                        End If
+                        If ReqVersionHead.StartsWith("1.") AndAlso ReqVersionHead.Contains("-") Then ReqVersionHead = ReqVersionHead.Substring(ReqVersionHead.LastIndexOf("-") + 1)
+                        If ReqVersionTail.StartsWith("1.") AndAlso ReqVersionTail.Contains("-") Then ReqVersionTail = ReqVersionTail.Substring(ReqVersionTail.LastIndexOf("-") + 1)
+                        '获取报错描述文本
+                        Dim VersionRequire As String
+                        If ReqVersionHead = ReqVersionTail Then
+                            VersionRequire = "应为 " & ReqVersionHead
+                        ElseIf ReqVersionHead.Contains(".") AndAlso ReqVersionTail.Contains(".") Then
+                            VersionRequire = "应为 " & ReqVersionHead & " 至 " & ReqVersionTail
+                        ElseIf ReqVersionHead.Contains(".") Then
+                            If ReqVersionHeadCanEqual Then
+                                VersionRequire = "最低应为 " & ReqVersionHead
+                            Else
+                                VersionRequire = "应高于 " & ReqVersionHead
+                            End If
+                        ElseIf ReqVersionTail.Contains(".") Then
+                            If ReqVersionTailCanEqual Then
+                                VersionRequire = "最高应为 " & ReqVersionHead
+                            Else
+                                VersionRequire = "应低于 " & ReqVersionHead
+                            End If
+                        Else
+                            VersionRequire = ""
+                        End If
+                        '检查前置 Mod 是否存在,并获取其版本
+                        If Not CurrentDependencies.ContainsKey(ReqId) Then
+                            Result.Add("缺少前置 Mod:" & ReqId & If(VersionRequire = "", "", ",其版本" & VersionRequire) & "。" & vbCrLf & " - " & ModEntity.FileName)
+                            Continue For
+                        End If
+                        Dim CurrentVersion As String = If(CurrentDependencies(ReqId)(0), "0.0")
+                        If CurrentVersion.StartsWith("1.") AndAlso CurrentVersion.Contains("-") Then CurrentVersion = CurrentVersion.Substring(CurrentVersion.LastIndexOf("-") + 1)
+                        '对比前置 Mod 头部版本
+                        If ReqVersionHead.Contains(".") Then
+                            If VersionSortInteger(ReqVersionHead, CurrentVersion) > If(ReqVersionHeadCanEqual, 0, -1) Then
+                                Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过低,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf &
+                                           " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", vbCrLf & " - 前置:" & CurrentDependencies(ReqId)(1), ""))
+                                Continue For
+                            End If
+                        End If
+                        '对比前置 Mod 尾部版本
+                        If ReqVersionTail.Contains(".") Then
+                            If VersionSortInteger(CurrentVersion, ReqVersionTail) > If(ReqVersionTailCanEqual, 0, -1) Then
+                                Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过高,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf &
+                                           " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", vbCrLf & " - 前置:" & CurrentDependencies(ReqId)(1), ""))
+                                Continue For
+                            End If
+                        End If
+                    Else
+                        If Not CurrentDependencies.ContainsKey(Dependency.Key) Then
+                            Result.Add("缺少前置 Mod:" & Dependency.Key & "。" & vbCrLf & " - " & ModEntity.FileName)
+                            Continue For
+                        End If
+                    End If
+                Next
+            Catch ex As Exception
+                Result.Add("检查 Mod 时出错:" & GetExceptionSummary(ex) & vbCrLf & " - " & ModEntity.FileName)
+                Log(ex, "检查 Mod 时出错")
+            End Try
+        Next
+        If Result.Count = 0 Then
+            Log("[Minecraft] Mod 检查未发现异常")
+        Else
+            Log("[Minecraft] Mod 检查异常结果:" & vbCrLf & Join(Result, vbCrLf))
+        End If
+        Return Result
+    End Function
+#End If
+
+End Module

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

@@ -254,17 +254,21 @@ Retry:
                 For Each ModJson In Task.Input
                     '跳过重复的 Mod(疑似 CurseForge Bug)
                     If FileList.ContainsKey(ModJson("id").ToObject(Of Integer)) Then Continue For
-                    '根据 modules 判断是资源包还是 Mod
+                    '建立 CompFile
+                    Dim File As New CompFile(ModJson, CompType.Mod)
+                    If Not File.Available Then Continue For
+                    '根据 modules 和文件名后缀判断是资源包还是 Mod
                     Dim IsResourcePack As Boolean
                     If ModJson("modules").Any Then 'modules 可能返回 null(#1006)
                         Dim ModuleNames = CType(ModJson("modules"), JArray).Select(Function(l) l("name").ToString).ToList
-                        IsResourcePack = Not ModuleNames.Contains("META-INF") AndAlso ModuleNames.Contains("pack.mcmeta")
+                        IsResourcePack =
+                            (Not ModuleNames.Contains("META-INF")) AndAlso (Not ModuleNames.Contains("mcmod.info")) AndAlso '不包含 META-INF 或 mcmod.info
+                            ModuleNames.Contains("pack.mcmeta") AndAlso '包含 pack.mcmeta
+                            (Not File.FileName.EndsWith(".jar")) '文件后缀不是 .jar
                     Else
                         IsResourcePack = False
                     End If
                     '实际的添加
-                    Dim File As New CompFile(ModJson, CompType.Mod)
-                    If Not File.Available Then Continue For
                     FileList.Add(ModJson("id"),
                                  File.ToNetFile($"{PathMcFolder}versions\{VersionName}\{If(IsResourcePack, "resourcepacks", "mods")}\"))
                     Task.Progress += 1 / (1 + ModFileList.Count)
@@ -278,6 +282,7 @@ Retry:
         '构造加载器
         Dim Request As New McInstallRequest With {
             .TargetVersionName = VersionName,
+            .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
             .MinecraftName = Json("minecraft")("version").ToString,
             .ForgeVersion = ForgeVersion,
             .FabricVersion = FabricVersion
@@ -333,7 +338,7 @@ Retry:
 
         '启动
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
-        Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
+        Loader.Start(Request.TargetVersionFolder)
         LoaderTaskbarAdd(Loader)
         FrmMain.BtnExtraDownload.ShowRefresh()
         If ShowRibble Then FrmMain.BtnExtraDownload.Ribble()
@@ -431,6 +436,7 @@ Retry:
         '构造加载器
         Dim Request As New McInstallRequest With {
             .TargetVersionName = VersionName,
+            .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
             .MinecraftName = MinecraftVersion,
             .ForgeVersion = ForgeVersion,
             .FabricVersion = FabricVersion
@@ -486,7 +492,7 @@ Retry:
 
         '启动
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
-        Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
+        Loader.Start(Request.TargetVersionFolder)
         LoaderTaskbarAdd(Loader)
         FrmMain.BtnExtraDownload.ShowRefresh()
         If ShowRibble Then FrmMain.BtnExtraDownload.Ribble()
@@ -533,6 +539,7 @@ Retry:
         End If
         Dim Request As New McInstallRequest With {
             .TargetVersionName = VersionName,
+            .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
             .MinecraftName = Json("gameVersion").ToString
         }
         Dim InstallExpectTime As Double = 0
@@ -584,7 +591,7 @@ Retry:
         '启动
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
         'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件”
-        Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
+        Loader.Start(Request.TargetVersionFolder)
         LoaderTaskbarAdd(Loader)
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.Ribble()
@@ -629,7 +636,7 @@ Retry:
             Hint("该整合包未提供游戏版本信息,无法安装!", HintType.Critical)
             Exit Sub
         End If
-        Dim Request As New McInstallRequest With {.TargetVersionName = VersionName}
+        Dim Request As New McInstallRequest With {.TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\"}
         For Each Component In PackJson("components")
             Select Case If(Component("uid"), "").ToString
                 Case "org.lwjgl"
@@ -679,7 +686,7 @@ Retry:
 
         '启动
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
-        Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
+        Loader.Start(Request.TargetVersionFolder)
         LoaderTaskbarAdd(Loader)
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.Ribble()
@@ -738,6 +745,7 @@ Retry:
         End If
         Dim Request As New McInstallRequest With {
             .TargetVersionName = VersionName,
+            .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
             .MinecraftName = Addons("game"),
             .OptiFineVersion = If(Addons.ContainsKey("optifine"), Addons("optifine"), Nothing),
             .ForgeVersion = If(Addons.ContainsKey("forge"), Addons("forge"), Nothing),
@@ -777,7 +785,7 @@ Retry:
         '启动
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
         'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件”
-        Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
+        Loader.Start(Request.TargetVersionFolder)
         LoaderTaskbarAdd(Loader)
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.Ribble()

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

@@ -116,7 +116,7 @@
             Get
                 Return _State
             End Get
-            Set(ByVal value As MinecraftState)
+            Set(value As MinecraftState)
                 If _State = value Then Exit Property
                 _State = value
                 WatcherStateChanged()

+ 11 - 5
Plain Craft Launcher 2/Modules/ModEvent.vb

@@ -13,11 +13,11 @@
 
                 Case "打开网页"
                     Data(0) = Data(0).Replace("\", "/")
-                    If Not (Data(0).StartsWith("http://") OrElse Data(0).StartsWith("https://")) Then
-                        MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "如果想要启动程序,请将 EventType 改为 打开文件。", "事件执行失败")
+                    If Not Data(0).Contains("://") OrElse Data(0).ToLower.StartsWith("file") Then '为了支持更多协议(#2200)
+                        MyMsgBox("EventData 必须为一个网址。" & vbCrLf & "如果想要启动程序,请将 EventType 改为 打开文件。", "事件执行失败")
                         Exit Sub
                     End If
-                    Hint("正在打开网页,请稍候……")
+                    Hint("正在开启中,请稍候……")
                     OpenWebsite(Data(0))
 
                 Case "打开文件", "打开帮助"
@@ -65,15 +65,21 @@
                     RunInThread(Sub() PageOtherTest.MemoryOptimize(True))
 
                 Case "清理垃圾"
-                    PageOtherTest.RubbishClear()
+                    RunInThread(Sub() PageOtherTest.RubbishClear())
 
                 Case "弹出窗口"
                     MyMsgBox(Data(1).Replace("\n", vbCrLf), Data(0).Replace("\n", vbCrLf))
 
+                Case "切换页面"
+                    FrmMain.PageChange(Val(Data(0)), Val(Data(1)))
+
+                Case "导入整合包"
+                    RunInThread(Sub() ModpackInstall())
+
                 Case "下载文件"
                     Data(0) = Data(0).Replace("\", "/")
                     If Not (Data(0).StartsWith("http://") OrElse Data(0).StartsWith("https://")) Then
-                        MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的协议。", "事件执行失败")
+                        MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的下载协议。", "事件执行失败")
                         Exit Sub
                     End If
                     PageOtherTest.StartCustomDownload(Data(0), GetFileNameFromPath(Data(0)))

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

@@ -25,7 +25,7 @@
                 For Each File In My.Computer.FileSystem.GetFiles(Path & "PCL\Musics\", FileIO.SearchOption.SearchAllSubDirectories, "*.*")
                     '文件夹可能会被加入 .ini 文件夹配置文件、一些乱七八糟的 .jpg 文件啥的
                     Dim Extend As String = File.Split(".").Last.ToLower
-                    If Not (Extend = "ini" OrElse Extend = "jpg" OrElse Extend = "txt" OrElse Extend = "cfg" OrElse Extend = "png") Then
+                    If Not (Extend = "ini" OrElse Extend = "jpg" OrElse Extend = "txt" OrElse Extend = "cfg" OrElse Extend = "lrc" OrElse Extend = "png") Then
                         MusicAllList.Add(File)
                     End If
                 Next
@@ -277,7 +277,7 @@
             Log(ex, "播放音乐失败(" & MusicCurrent & ")", LogLevel.Developer)
             If MusicAllList.Count > 1 Then
                 Thread.Sleep(1000)
-                MusicStartPlay(DequeueNextMusicAddress)
+                MusicStartPlay(DequeueNextMusicAddress, IsFirstLoad)
             End If
         Finally
             If CurrentWave IsNot Nothing Then CurrentWave.Dispose()

+ 4 - 4
Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb

@@ -25,7 +25,7 @@ Public Class CookieWebClient
     ''' </summary>
     Public Timeout As Integer = 600000
 
-    Protected Overrides Function GetWebRequest(ByVal address As Uri) As WebRequest
+    Protected Overrides Function GetWebRequest(address As Uri) As WebRequest
         Dim r As WebRequest = MyBase.GetWebRequest(address)
         Dim request = TryCast(r, HttpWebRequest)
 
@@ -37,19 +37,19 @@ Public Class CookieWebClient
         Return r
     End Function
 
-    Protected Overrides Function GetWebResponse(ByVal request As WebRequest, ByVal result As IAsyncResult) As WebResponse
+    Protected Overrides Function GetWebResponse(request As WebRequest, result As IAsyncResult) As WebResponse
         Dim response As WebResponse = MyBase.GetWebResponse(request, result)
         ReadCookies(response)
         Return response
     End Function
 
-    Protected Overrides Function GetWebResponse(ByVal request As WebRequest) As WebResponse
+    Protected Overrides Function GetWebResponse(request As WebRequest) As WebResponse
         Dim response As WebResponse = MyBase.GetWebResponse(request)
         ReadCookies(response)
         Return response
     End Function
 
-    Private Sub ReadCookies(ByVal r As WebResponse)
+    Private Sub ReadCookies(r As WebResponse)
         Dim response = TryCast(r, HttpWebResponse)
         If response IsNot Nothing Then
             Dim cookies As CookieCollection = response.Cookies

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

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

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

@@ -386,7 +386,7 @@ Public Module ModDownloadLib
         '选择 Java
         Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
         If Java Is Nothing Then
-            If Not JavaDownloadConfirm(17) Then Throw New Exception("由于未找到 Java,已取消安装。")
+            If Not JavaDownloadConfirm("Java 8 或更高版本") Then Throw New Exception("由于未找到 Java,已取消安装。")
             '开始自动下载
             Dim JavaLoader = JavaFixLoaders(17)
             JavaLoader.Start(17, IsForceRestart:=True)
@@ -489,7 +489,7 @@ Public Module ModDownloadLib
     ''' <summary>
     ''' 获取下载某个 OptiFine 版本的加载器列表。
     ''' </summary>
-    Private Function McDownloadOptiFineLoader(DownloadInfo As DlOptiFineListEntry, Optional McFolder As String = Nothing, Optional ClientDownloadLoader As LoaderCombo(Of String) = Nothing, Optional FixLibrary As Boolean = True) As List(Of LoaderBase)
+    Private Function McDownloadOptiFineLoader(DownloadInfo As DlOptiFineListEntry, Optional McFolder As String = Nothing, Optional ClientDownloadLoader As LoaderCombo(Of String) = Nothing, Optional ClientFolder As String = Nothing, Optional FixLibrary As Boolean = True) As List(Of LoaderBase)
 
         '参数初始化
         McFolder = If(McFolder, PathMcFolder)
@@ -553,14 +553,14 @@ Public Module ModDownloadLib
                                                                              '拷贝原版文件
                                                                              If Not IsCustomFolder Then Exit Sub
                                                                              SyncLock VanillaSyncLock
-                                                                                 Dim ClientName As String = New DirectoryInfo(ClientDownloadLoader.Input).Name '#2031 报告了此处 ClientDownloadLoader.Input 为 null 的问题,但无法复现
+                                                                                 Dim ClientName As String = GetFolderNameFromPath(ClientFolder)
                                                                                  Directory.CreateDirectory(McFolder & "versions\" & DownloadInfo.Inherit)
                                                                                  If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json") Then
-                                                                                     CopyFile(ClientDownloadLoader.Input & ClientName & ".json",
+                                                                                     CopyFile(ClientFolder & ClientName & ".json",
                                                                                           McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json")
                                                                                  End If
                                                                                  If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar") Then
-                                                                                     CopyFile(ClientDownloadLoader.Input & ClientName & ".jar",
+                                                                                     CopyFile(ClientFolder & ClientName & ".jar",
                                                                                               McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar")
                                                                                  End If
                                                                              End SyncLock
@@ -1114,7 +1114,7 @@ Public Module ModDownloadLib
         '选择 Java
         Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
         If Java Is Nothing Then
-            If Not JavaDownloadConfirm(17) Then Throw New Exception("由于未找到 Java,已取消安装。")
+            If Not JavaDownloadConfirm("Java 8 或更高版本") Then Throw New Exception("由于未找到 Java,已取消安装。")
             '开始自动下载
             Dim JavaLoader = JavaFixLoaders(17)
             JavaLoader.Start(17, IsForceRestart:=True)
@@ -1259,7 +1259,7 @@ Public Module ModDownloadLib
     ''' <summary>
     ''' 获取下载某个 Forge 版本的加载器列表。
     ''' </summary>
-    Private Function McDownloadForgeLoader(Version As String, Inherit As String, Optional DownloadInfo As DlForgeVersionEntry = Nothing, Optional McFolder As String = Nothing, Optional ClientDownloadLoader As LoaderCombo(Of String) = Nothing, Optional FixLibrary As Boolean = True) As List(Of LoaderBase)
+    Private Function McDownloadForgeLoader(Version As String, Inherit As String, Optional DownloadInfo As DlForgeVersionEntry = Nothing, Optional McFolder As String = Nothing, Optional ClientDownloadLoader As LoaderCombo(Of String) = Nothing, Optional ClientFolder As String = Nothing, Optional FixLibrary As Boolean = True) As List(Of LoaderBase)
 
         '参数初始化
         McFolder = If(McFolder, PathMcFolder)
@@ -1386,13 +1386,13 @@ Public Module ModDownloadLib
                                                                              '拷贝原版文件
                                                                              If Not IsCustomFolder Then Exit Sub
                                                                              SyncLock VanillaSyncLock
-                                                                                 Dim ClientName As String = New DirectoryInfo(ClientDownloadLoader.Input).Name
+                                                                                 Dim ClientName As String = GetFolderNameFromPath(ClientFolder)
                                                                                  Directory.CreateDirectory(McFolder & "versions\" & Inherit)
                                                                                  If Not File.Exists(McFolder & "versions\" & Inherit & "\" & Inherit & ".json") Then
-                                                                                     CopyFile(ClientDownloadLoader.Input & ClientName & ".json", McFolder & "versions\" & Inherit & "\" & Inherit & ".json")
+                                                                                     CopyFile(ClientFolder & ClientName & ".json", McFolder & "versions\" & Inherit & "\" & Inherit & ".json")
                                                                                  End If
                                                                                  If Not File.Exists(McFolder & "versions\" & Inherit & "\" & Inherit & ".jar") Then
-                                                                                     CopyFile(ClientDownloadLoader.Input & ClientName & ".jar", McFolder & "versions\" & Inherit & "\" & Inherit & ".jar")
+                                                                                     CopyFile(ClientFolder & ClientName & ".jar", McFolder & "versions\" & Inherit & "\" & Inherit & ".jar")
                                                                                  End If
                                                                              End SyncLock
 #End Region
@@ -1797,6 +1797,10 @@ Public Module ModDownloadLib
         ''' 必填。安装目标版本名称。
         ''' </summary>
         Public TargetVersionName As String
+        ''' <summary>
+        ''' 必填。安装目标文件夹。
+        ''' </summary>
+        Public TargetVersionFolder As String
 
         ''' <summary>
         ''' 必填。欲下载的 Minecraft 的版本名。
@@ -1909,7 +1913,7 @@ Public Module ModDownloadLib
             Dim Loader As New LoaderCombo(Of String)(Request.TargetVersionName & " 安装", SubLoaders) With {.OnStateChanged = AddressOf McInstallState}
 
             '启动
-            Loader.Start(PathMcFolder & "versions\" & Request.TargetVersionName & "\")
+            Loader.Start(Request.TargetVersionFolder)
             LoaderTaskbarAdd(Loader)
             FrmMain.BtnExtraDownload.ShowRefresh()
             FrmMain.BtnExtraDownload.Ribble()
@@ -2009,28 +2013,29 @@ Public Module ModDownloadLib
             If OptiFineAsMod Then
                 LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineSaveLoader(Request.OptiFineEntry, OptiFineFolder & Request.OptiFineEntry.NameFile)) With {.Show = False, .ProgressWeight = 16, .Block = Request.ForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
             Else
-                LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineLoader(Request.OptiFineEntry, TempMcFolder, ClientLoader, False)) With {.Show = False, .ProgressWeight = 24, .Block = Request.ForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
+                LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineLoader(Request.OptiFineEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder, False)) With {.Show = False, .ProgressWeight = 24, .Block = Request.ForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
             End If
         End If
         'Forge
         If Request.ForgeVersion IsNot Nothing Then
-            LoaderList.Add(New LoaderCombo(Of String)("下载 Forge " & Request.ForgeVersion, McDownloadForgeLoader(Request.ForgeVersion, Request.MinecraftName, Request.ForgeEntry, TempMcFolder, ClientLoader, False)) With {.Show = False, .ProgressWeight = 25, .Block = Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
+            LoaderList.Add(New LoaderCombo(Of String)("下载 Forge " & Request.ForgeVersion, McDownloadForgeLoader(Request.ForgeVersion, Request.MinecraftName, Request.ForgeEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder, False)) With {.Show = False, .ProgressWeight = 25, .Block = Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
         End If
         'LiteLoader
         If Request.LiteLoaderEntry IsNot Nothing Then
             LoaderList.Add(New LoaderCombo(Of String)("下载 LiteLoader " & Request.MinecraftName, McDownloadLiteLoaderLoader(Request.LiteLoaderEntry, TempMcFolder, ClientLoader, False)) With {.Show = False, .ProgressWeight = 1, .Block = Request.FabricVersion Is Nothing})
         End If
-        'Fabric Loader
+        'Fabric
         If Request.FabricVersion IsNot Nothing Then
             LoaderList.Add(New LoaderCombo(Of String)("下载 Fabric " & Request.FabricVersion, McDownloadFabricLoader(Request.FabricVersion, Request.MinecraftName, TempMcFolder, False)) With {.Show = False, .ProgressWeight = 2, .Block = True})
         End If
         '合并安装
-        LoaderList.Add(New LoaderTask(Of String, String)("安装游戏", Sub(Task As LoaderTask(Of String, String))
-                                                                     InstallMerge(OutputFolder, OutputFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, FabricFolder, LiteLoaderFolder)
-                                                                     Task.Progress = 0.3
-                                                                     If Directory.Exists(TempMcFolder & "libraries") Then CopyDirectory(TempMcFolder & "libraries", PathMcFolder & "libraries")
-                                                                     If Directory.Exists(TempMcFolder & "mods") Then CopyDirectory(TempMcFolder & "mods", PathMcFolder & "mods")
-                                                                 End Sub) With {.ProgressWeight = 2, .Block = True})
+        LoaderList.Add(New LoaderTask(Of String, String)("安装游戏",
+            Sub(Task As LoaderTask(Of String, String))
+                InstallMerge(OutputFolder, OutputFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, FabricFolder, LiteLoaderFolder)
+                Task.Progress = 0.3
+                If Directory.Exists(TempMcFolder & "libraries") Then CopyDirectory(TempMcFolder & "libraries", PathMcFolder & "libraries")
+                If Directory.Exists(TempMcFolder & "mods") Then CopyDirectory(TempMcFolder & "mods", PathMcFolder & "mods")
+            End Sub) With {.ProgressWeight = 2, .Block = True})
         '补全文件
         If Not DontFixLibraries AndAlso
         (Request.OptiFineEntry IsNot Nothing OrElse (Request.ForgeVersion IsNot Nothing AndAlso Request.ForgeVersion.Split(".")(0) >= 20) OrElse Request.FabricVersion IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing) Then

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

@@ -945,11 +945,16 @@
     ''' 获取 FabricApi 的加载异常信息。若正常则返回 Nothing。
     ''' </summary>
     Private Function LoadFabricApiGetError() As String
-        If SelectedFabric Is Nothing Then Return "需要安装 Fabric"
         If LoadFabricApi.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "正在获取版本列表……"
         If LoadFabricApi.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadFabricApi.State, Object).Error.Message
+        If DlFabricApiLoader.Output Is Nothing Then
+            If SelectedFabric Is Nothing Then Return "需要安装 Fabric"
+            Return "正在获取版本列表……"
+        End If
         For Each Version In DlFabricApiLoader.Output
-            If IsSuitableFabricApi(Version.DisplayName, SelectedMinecraftId) Then Return Nothing
+            If Not IsSuitableFabricApi(Version.DisplayName, SelectedMinecraftId) Then Continue For
+            If SelectedFabric Is Nothing Then Return "需要安装 Fabric"
+            Return Nothing
         Next
         Return "没有可用版本"
     End Function
@@ -1032,14 +1037,21 @@
     ''' 获取 OptiFabric 的加载异常信息。若正常则返回 Nothing。
     ''' </summary>
     Private Function LoadOptiFabricGetError() As String
-        If SelectedFabric Is Nothing AndAlso SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine 与 Fabric"
-        If SelectedFabric Is Nothing Then Return "需要安装 Fabric"
-        If SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine"
         If SelectedMinecraftId.StartsWith("1.14") OrElse SelectedMinecraftId.StartsWith("1.15") Then Return "不兼容老版本 Fabric,请手动下载 OptiFabric Origins"
         If LoadOptiFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "正在获取版本列表……"
         If LoadOptiFabric.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadOptiFabric.State, Object).Error.Message
+        If DlOptiFabricLoader.Output Is Nothing Then
+            If SelectedFabric Is Nothing AndAlso SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine 与 Fabric"
+            If SelectedFabric Is Nothing Then Return "需要安装 Fabric"
+            If SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine"
+            Return "正在获取版本列表……"
+        End If
         For Each Version In DlOptiFabricLoader.Output
-            If IsSuitableOptiFabric(Version, SelectedMinecraftId) Then Return Nothing
+            If Not IsSuitableOptiFabric(Version, SelectedMinecraftId) Then Continue For '2135#
+            If SelectedFabric Is Nothing AndAlso SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine 与 Fabric"
+            If SelectedFabric Is Nothing Then Return "需要安装 Fabric"
+            If SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine"
+            Return Nothing '通过检查
         Next
         Return "没有可用版本"
     End Function
@@ -1114,6 +1126,7 @@
         '提交安装申请
         Dim Request As New McInstallRequest With {
             .TargetVersionName = TextSelectName.Text,
+            .TargetVersionFolder = $"{PathMcFolder}versions\{TextSelectName.Text}\",
             .MinecraftJson = SelectedMinecraftJsonUrl,
             .MinecraftName = SelectedMinecraftId,
             .OptiFineEntry = SelectedOptiFine,

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

@@ -5,7 +5,7 @@
 
     '皮肤储存
     Private _Address As String
-    Public Property Address() As String
+    Public Property Address As String
         Get
             Return _Address
         End Get
@@ -76,12 +76,19 @@
     ''' </summary>
     Public Sub Load()
         Try
-            Address = Loader.Output
             '检查文件存在
-            If Address = "" Then Throw New Exception("皮肤加载器 " & Loader.Name & " 没有输出")
+            Address = Loader.Output
+            If String.IsNullOrEmpty(Address) Then Throw New Exception("皮肤加载器 " & Loader.Name & " 没有输出")
             If Not Address.StartsWith(PathImage) AndAlso Not File.Exists(Address) Then Throw New FileNotFoundException("皮肤文件未找到", Address)
             '加载
-            Dim Image As New MyBitmap(Address)
+            Dim Image As MyBitmap
+            Try
+                Image = New MyBitmap(Address)
+            Catch ex As Exception '#2272
+                Log(ex, $"皮肤文件已损坏:{Address}", LogLevel.Hint)
+                File.Delete(Address)
+                Exit Sub
+            End Try
             ImgBack.Tag = Address
             '大小检查
             Dim Scale As Integer = Image.Pic.Width / 64

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

@@ -17,6 +17,7 @@
 
     Private IsLoad As Boolean = False
     Private Sub OnLoaded() Handles Me.Loaded
+        FormMain.EndProgramForce(Result.Aborted)
         If IsLoad Then Exit Sub
         IsLoad = True
         '启动监视线程

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

@@ -11,6 +11,7 @@ Public Class PageLinkIoi
     '页面初始化
     Private IsLoad As Boolean = False
     Private Sub MeLoaded() Handles Me.Loaded
+        FormMain.EndProgramForce(Result.Aborted)
 
         '重复加载部分
         PanBack.ScrollToHome()

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

@@ -187,6 +187,7 @@
                     End Try
                     FrmSpeedRight.PanMain.Children.Insert(0, Card)
                     RightCards.Add(Loader.Name, Card)
+                    Log($"[Watcher] 新建下载管理卡片:{Loader.Name}")
                     '添加取消按钮
                     Dim Cancel As New MyIconButton With {.Name = "BtnCancel", .Logo = "F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z", .Height = 20, .Margin = New Thickness(0, 10, 10, 0), .LogoScale = 1.1, .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Top}
                     Card.Children.Add(Cancel)
@@ -194,6 +195,8 @@
                                                  AniDispose(sender, False)
                                                  AniDispose(Card, True, Sub() If FrmSpeedRight.PanMain.Children.Count = 0 AndAlso FrmMain.PageCurrent = FormMain.PageType.DownloadManager Then FrmMain.PageBack())
                                                  RightCards.Remove(Loader.Name)
+                                                 LoaderTaskbar.Remove(Loader)
+                                                 Log($"[Watcher] 关闭下载管理卡片:{Loader.Name}")
                                                  RunInThread(Sub() Loader.Abort())
                                              End Sub
                     '如果已经失败,再刷新一次,修改成失败的控件
@@ -217,6 +220,7 @@
                             Dim Card As Grid = RightCards(Loader.Name)
                             FrmSpeedRight.PanMain.Children.Remove(Card)
                             RightCards.Remove(Loader.Name)
+                            Log($"[Watcher] 移除下载管理卡片:{Loader.Name}")
                         End Sub)
         End If
     End Sub

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

@@ -7,22 +7,20 @@
     <Grid>
         <Grid x:Name="PanAllBack">
             <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-                <StackPanel x:Name="PanMain" Margin="25,10" Grid.IsSharedSizeScope="True">
+                <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 文件拖入窗口可以快速安装 Mod。" TextWrapping="Wrap" />
+                            <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" />
-                                    <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="BtnManageEnabled" Grid.Column="1" MinWidth="140" Text="全部启用" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
-                                <local:MyButton x:Name="BtnManageDisabled" Grid.Column="2" MinWidth="140" Text="全部禁用" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
-                                <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" />
+                                <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>
@@ -47,5 +45,22 @@
         <local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40,0" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False">
             <local:MyLoading Text="正在加载 Mod 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
         </local:MyCard>
+        <local:MyCard x:Name="CardSelect" Visibility="Collapsed" Opacity="0"
+                      HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="25,25,25,0" UseAnimation="False" HasMouseAnimation="False">
+            <local:MyCard.RenderTransform>
+                <TranslateTransform x:Name="TransSelect" Y="-10" />
+            </local:MyCard.RenderTransform>
+            <TextBlock x:Name="LabSelect" Text="已选择 8 个文件" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="9" Foreground="{DynamicResource ColorBrush2}" />
+            <StackPanel Orientation="Horizontal" Margin="5,28,5,5">
+                <local:MyIconTextButton x:Name="BtnSelectEnable" Text="启用"
+                                        LogoScale="1.05" Logo="M512 0a512 512 0 1 0 512 512A512 512 0 0 0 512 0z m0 921.6a409.6 409.6 0 1 1 409.6-409.6 409.6 409.6 0 0 1-409.6 409.6z M716.8 339.968l-256 253.44L328.192 460.8A51.2 51.2 0 0 0 256 532.992l168.448 168.96a51.2 51.2 0 0 0 72.704 0l289.28-289.792A51.2 51.2 0 0 0 716.8 339.968z" />
+                <local:MyIconTextButton x:Name="BtnSelectDisable" Text="禁用"
+                                        LogoScale="0.98" Logo="M512 0a512 512 0 1 0 512 512A512 512 0 0 0 512 0z m409.6 512a404.48 404.48 0 0 1-86.528 250.88L261.12 188.928A404.48 404.48 0 0 1 512 102.4a409.6 409.6 0 0 1 409.6 409.6zM102.4 512a404.48 404.48 0 0 1 86.528-250.88L762.88 835.072A404.48 404.48 0 0 1 512 921.6a409.6 409.6 0 0 1-409.6-409.6z" />
+                <local:MyIconTextButton x:Name="BtnSelectDelete" Text="删除"
+                                        LogoScale="0.96" Logo="M520.192 0C408.43 0 317.44 82.87 313.563 186.734H52.736c-29.038 0-52.663 21.943-52.663 49.079s23.625 49.152 52.663 49.152h58.075v550.473c0 103.35 75.118 187.757 167.717 187.757h472.43c92.599 0 167.716-83.894 167.716-187.757V285.477h52.59c29.038 0 52.59-21.943 52.663-49.08-0.073-27.135-23.625-49.151-52.663-49.151H726.235C723.237 83.017 631.955 0 520.192 0zM404.846 177.957c3.803-50.03 50.176-89.015 107.447-89.015 57.197 0 103.57 38.985 106.788 89.015H404.92zM284.379 933.669c-33.353 0-69.997-39.351-69.997-95.525v-549.01H833.39v549.522c0 56.247-36.645 95.525-69.998 95.525H284.379v-0.512z M357.23 800.695a48.274 48.274 0 0 0 47.616-49.006V471.7a48.274 48.274 0 0 0-47.543-49.08 48.274 48.274 0 0 0-47.69 49.006V751.69c0 27.282 20.846 49.006 47.617 49.006z m166.62 0a48.274 48.274 0 0 0 47.688-49.006V471.7a48.274 48.274 0 0 0-47.689-49.08 48.274 48.274 0 0 0-47.543 49.006V751.69c0 27.282 21.431 49.006 47.543 49.006z m142.92 0a48.274 48.274 0 0 0 47.543-49.006V471.7a48.274 48.274 0 0 0-47.543-49.08 48.274 48.274 0 0 0-47.616 49.006V751.69c0 27.282 20.773 49.006 47.543 49.006z" />
+                <local:MyIconTextButton x:Name="BtnSelectCancel" Text="取消选择"
+                                        LogoScale="0.8" Logo="M867.648 951.296 512 595.648l-355.648 355.648c-11.52 11.52-30.272 11.52-41.856 0L72.64 909.44c-11.52-11.52-11.52-30.272 0-41.856L428.352 512 72.64 156.352c-11.52-11.52-11.52-30.272 0-41.856l41.856-41.856c11.52-11.52 30.272-11.52 41.856 0L512 428.288l355.648-355.648c11.52-11.52 30.272-11.52 41.856 0l41.856 41.856c11.52 11.52 11.52 30.272 0 41.856L595.648 512l355.648 355.648c11.52 11.52 11.52 30.272 0 41.856l-41.856 41.856C897.984 962.88 879.168 962.88 867.648 951.296L867.648 951.296z" />
+            </StackPanel>
+        </local:MyCard>
     </Grid>
 </local:MyPageRight>

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

@@ -1,4 +1,7 @@
-Public Class PageVersionMod
+Imports System.Security.Principal
+Imports PCL.MyLoading
+
+Public Class PageVersionMod
 
 #Region "初始化"
 
@@ -6,7 +9,11 @@
     Public Sub PageOther_Loaded() Handles Me.Loaded
 
         PanBack.ScrollToHome()
+        AniControlEnabled += 1
+        SelectedMods.Clear()
         RefreshList()
+        ChangeAllSelected(False)
+        AniControlEnabled -= 1
 
         '非重复加载部分
         If IsLoad Then Exit Sub
@@ -42,6 +49,7 @@
 
 #Region "UI 化"
 
+    Private PanItems As StackPanel
     ''' <summary>
     ''' 将 Mod 列表加载为 UI。
     ''' </summary>
@@ -62,13 +70,13 @@
 
             SearchBox.Text = ""
             '建立 StackPanel
-            Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, If(List.Count > 0, 20, 0)), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)}
+            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)}
             For Each ModEntity As McMod In List
-                NewStack.Children.Add(McModListItem(ModEntity))
+                PanItems.Children.Add(McModListItem(ModEntity))
             Next
             '建立 MyCard
             Dim NewCard As New MyCard With {.Title = McModGetTitle(List), .Margin = New Thickness(0, 0, 0, 15)}
-            NewCard.Children.Add(NewStack)
+            NewCard.Children.Add(PanItems)
             PanList.Children.Add(NewCard)
             '显示提示
             If List.Count > 0 AndAlso Not Setup.Get("HintModDisable") Then
@@ -88,20 +96,12 @@
         For Each ModEntity As McMod In List
             Counter(ModEntity.State) += 1
         Next
-        If List.Count = 0 Then
-            '全空
-            Return "未找到任何 Mod"
-        ElseIf Counter(McMod.McModState.Disabled) = 0 AndAlso Counter(McMod.McModState.Unavaliable) = 0 Then
-            '只有启用的 Mod
-            Return "Mod 列表(" & Counter(McMod.McModState.Fine) & ")"
-        Else
-            '混合种类
-            Dim TypeList As New List(Of String)
-            If Counter(McMod.McModState.Fine) > 0 Then TypeList.Add("启用 " & Counter(McMod.McModState.Fine))
-            If Counter(McMod.McModState.Disabled) > 0 Then TypeList.Add("禁用 " & Counter(McMod.McModState.Disabled))
-            If Counter(McMod.McModState.Unavaliable) > 0 Then TypeList.Add("错误 " & Counter(McMod.McModState.Unavaliable))
-            Return "Mod 列表(" & Join(TypeList, ",") & ")"
-        End If
+        If List.Count = 0 Then Return "未找到任何 Mod"
+        Dim TypeList As New List(Of String)
+        If Counter(McMod.McModState.Fine) > 0 Then TypeList.Add("启用 " & Counter(McMod.McModState.Fine))
+        If Counter(McMod.McModState.Disabled) > 0 Then TypeList.Add("禁用 " & Counter(McMod.McModState.Disabled))
+        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
         '图标
@@ -138,16 +138,21 @@
         '    Desc += " : " & Join(DepList, ", ")
         'End If
         '实例化
-        Dim NewItem As New MyListItem With {.Logo = Logo, .SnapsToDevicePixels = True, .Title = Title, .Info = Desc, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry}
-        NewItem.PaddingRight = If(Entry.IsFileAvailable, 73, 55)
-        '事件
-        NewItem.ContentHandler = AddressOf McModContent
+        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)
+        }
+        AniControlEnabled -= 1
         Return NewItem
     End Function
     Private Sub McModContent(sender As MyListItem, e As EventArgs)
         Dim ModEntity As McMod = sender.Tag
         '注册点击事件
-        AddHandler sender.Click, AddressOf Item_Click
+        AddHandler sender.Changed, AddressOf CheckChanged
+        AddHandler sender.LogoClick, AddressOf ItemLogo_Click
         '图标按钮
         Dim BtnDel As New MyIconButton With {.LogoScale = 1.1, .Logo = Logo.IconButtonDelete, .Tag = sender}
         BtnDel.ToolTip = "删除"
@@ -161,18 +166,14 @@
         ToolTipService.SetVerticalOffset(BtnOpen, 30)
         ToolTipService.SetHorizontalOffset(BtnOpen, 2)
         AddHandler BtnOpen.Click, AddressOf Open_Click
-        If ModEntity.IsFileAvailable Then
-            Dim BtnCont As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .Tag = sender}
-            BtnCont.ToolTip = "详情"
-            ToolTipService.SetPlacement(BtnCont, Primitives.PlacementMode.Center)
-            ToolTipService.SetVerticalOffset(BtnCont, 30)
-            ToolTipService.SetHorizontalOffset(BtnCont, 2)
-            AddHandler BtnCont.Click, AddressOf Info_Click
-            AddHandler sender.MouseRightButtonDown, AddressOf Info_Click
-            sender.Buttons = {BtnCont, BtnOpen, BtnDel}
-        Else
-            sender.Buttons = {BtnOpen, BtnDel}
-        End If
+        Dim BtnCont As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .Tag = sender}
+        BtnCont.ToolTip = "详情"
+        ToolTipService.SetPlacement(BtnCont, Primitives.PlacementMode.Center)
+        ToolTipService.SetVerticalOffset(BtnCont, 30)
+        ToolTipService.SetHorizontalOffset(BtnCont, 2)
+        AddHandler BtnCont.Click, AddressOf Info_Click
+        AddHandler sender.MouseRightButtonDown, AddressOf Info_Click
+        sender.Buttons = {BtnCont, BtnOpen, BtnDel}
     End Sub
 
 #End Region
@@ -210,13 +211,144 @@
 #End If
 
     ''' <summary>
-    ''' 一键启用 / 禁用。
+    ''' 全选。
     ''' </summary>
-    Private Sub BtnManageChange_Click(sender As MyButton, e As EventArgs) Handles BtnManageEnabled.Click, BtnManageDisabled.Click
+    Private Sub BtnManageSelectAll_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageSelectAll.Click
+        ChangeAllSelected(True)
+    End Sub
+
+#End Region
+
+#Region "选择"
+
+    '选择的 Mod 的路径(不含 .disabled)
+    Public SelectedMods As New List(Of String)
+
+    '单项切换选择状态
+    Public Sub CheckChanged(sender As MyListItem, e As RouteEventArgs)
+        If AniControlEnabled <> 0 Then Return
+        '更新选择了的内容
+        Dim SelectedKey As String = CType(sender.Tag, McMod).RawFileName
+        If sender.Checked Then
+            If Not SelectedMods.Contains(SelectedKey) Then SelectedMods.Add(SelectedKey)
+        Else
+            SelectedMods.Remove(SelectedKey)
+        End If
+        '更新下边栏 UI
+        RefreshBottomBar()
+    End Sub
+
+    '改变下边栏状态
+    Private ShownCount As Integer = 0
+    Private Sub RefreshBottomBar()
+        '计数
+        Dim NewCount As Integer = SelectedMods.Count
+        Dim Selected = NewCount > 0
+        If Selected Then LabSelect.Text = $"已选择 {NewCount} 个文件" '取消所有选择时不更新数字
+        '按钮可用性
+        If Selected Then
+            Dim HasEnabled As Boolean = False
+            Dim HasDisabled As Boolean = False
+            For Each ModEntity In McModLoader.Output
+                If SelectedMods.Contains(ModEntity.RawFileName) Then
+                    If ModEntity.State = McMod.McModState.Fine Then
+                        HasEnabled = True
+                    ElseIf ModEntity.State = McMod.McModState.Disabled Then
+                        HasDisabled = True
+                    End If
+                End If
+            Next
+            BtnSelectDisable.IsEnabled = HasEnabled
+            BtnSelectEnable.IsEnabled = HasDisabled
+        End If
+        '更新显示状态
+        CardSelect.IsHitTestVisible = Selected
+        If AniControlEnabled = 0 Then
+            If Selected Then
+                '仅在数量增加时播放出现/跳跃动画
+                If ShownCount >= NewCount Then
+                    ShownCount = NewCount
+                    Return
+                Else
+                    ShownCount = NewCount
+                End If
+                '出现/跳跃动画
+                CardSelect.Visibility = Visibility.Visible
+                AniStart({
+                    AaOpacity(CardSelect, 1 - CardSelect.Opacity, 60),
+                    AaTranslateY(CardSelect, -27 - TransSelect.Y, 120, Ease:=New AniEaseOutFluent(AniEasePower.Weak)),
+                    AaTranslateY(CardSelect, 3, 150, 120, Ease:=New AniEaseInoutFluent(AniEasePower.Weak)),
+                    AaTranslateY(CardSelect, -1, 90, 270, Ease:=New AniEaseInoutFluent(AniEasePower.Weak))
+                }, "Mod Sidebar")
+            Else
+                '不重复播放隐藏动画
+                If ShownCount = 0 Then Return
+                ShownCount = 0
+                '隐藏动画
+                AniStart({
+                    AaOpacity(CardSelect, -CardSelect.Opacity, 90),
+                    AaTranslateY(CardSelect, -10 - TransSelect.Y, 90, Ease:=New AniEaseInFluent(AniEasePower.Weak)),
+                    AaCode(Sub() CardSelect.Visibility = Visibility.Collapsed, After:=True)
+                }, "Mod Sidebar")
+            End If
+        Else
+            AniStop("Mod Sidebar")
+            ShownCount = NewCount
+            If Selected Then
+                CardSelect.Visibility = Visibility.Visible
+                CardSelect.Opacity = 1
+                TransSelect.Y = -25
+            Else
+                CardSelect.Visibility = Visibility.Collapsed
+                CardSelect.Opacity = 0
+                TransSelect.Y = -10
+            End If
+        End If
+    End Sub
+
+    '切换所有项的选择状态
+    Private Sub ChangeAllSelected(Value As Boolean)
+        AniControlEnabled += 1
+        SelectedMods.Clear()
+        If IsSearching Then
+            '搜索中
+            For Each Item As MyListItem In PanSearchList.Children
+                Item.Checked = Value
+                If Value Then SelectedMods.Add(CType(Item.Tag, McMod).RawFileName)
+            Next
+            If Not Value Then '只取消选择
+                If PanItems IsNot Nothing Then
+                    For Each Item As MyListItem In PanItems.Children
+                        Item.Checked = Value
+                    Next
+                End If
+            End If
+        Else
+            '非搜索中
+            If PanItems IsNot Nothing Then
+                For Each Item As MyListItem In PanItems.Children
+                    Item.Checked = Value
+                    If Value Then SelectedMods.Add(CType(Item.Tag, McMod).RawFileName)
+                Next
+            End If
+        End If
+        AniControlEnabled -= 1
+        '更新下边栏 UI
+        RefreshBottomBar()
+    End Sub
+    Private Sub Load_State(sender As Object, newState As MyLoadingState, oldState As MyLoadingState) Handles Load.StateChanged
+        ChangeAllSelected(False)
+    End Sub
+
+#End Region
+
+#Region "下边栏"
+
+    '启用 / 禁用
+    Private Sub BtnSelectEorD_Click(sender As MyIconTextButton, e As RouteEventArgs) Handles BtnSelectEnable.Click, BtnSelectDisable.Click
         Dim IsSuccessful As Boolean = True
-        Dim SuccessfulCount As Integer = 0
-        Dim IsDisable As Boolean = sender.Text.Contains("禁用")
-        For Each ModEntity In McModLoader.Output
+        Dim IsDisable As Boolean = sender.Equals(BtnSelectDisable)
+        For Each ModEntity In McModLoader.Output.Where(Function(m) SelectedMods.Contains(m.RawFileName)).ToList()
             Dim NewPath As String = Nothing
             If ModEntity.State = McMod.McModState.Fine And IsDisable Then
                 '禁用
@@ -225,7 +357,6 @@
                 '启用
                 NewPath = ModEntity.Path.Substring(0, ModEntity.Path.Count - ".disabled".Count)
             Else
-                SuccessfulCount += 1
                 Continue For
             End If
             '重命名
@@ -233,27 +364,99 @@
                 If File.Exists(NewPath) AndAlso Not File.Exists(ModEntity.Path) Then Continue For '因为未知原因 Mod 的状态已经切换完了
                 File.Delete(NewPath)
                 FileSystem.Rename(ModEntity.Path, NewPath)
-                SuccessfulCount += 1
             Catch ex As FileNotFoundException
-                Log(ex, "未找到需要重命名的 Mod(" & If(ex.FileName, "null") & ")", LogLevel.Feedback)
-                RefreshList()
-                Exit Sub
+                Log(ex, $"未找到需要重命名的 Mod({If(ModEntity.Path, "null")})", LogLevel.Feedback)
+                RefreshList(True)
+                Return
             Catch ex As Exception
-                Log(ex, "全局状态改变中重命名 Mod 失败")
+                Log(ex, $"批量重命名 Mod 失败({If(ModEntity.Path, "null")})")
                 IsSuccessful = False
             End Try
+            '更改 Loader 和 UI 中的列表
+            Dim NewModEntity As New McMod(NewPath)
+            Dim IndexOfLoader As Integer = McModLoader.Output.IndexOf(ModEntity)
+            McModLoader.Output.RemoveAt(IndexOfLoader)
+            McModLoader.Output.Insert(IndexOfLoader, NewModEntity)
+            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))
+            Parent.Children.RemoveAt(IndexOfUi)
+            Parent.Children.Insert(IndexOfUi, McModListItem(NewModEntity))
         Next
-        If Not IsSuccessful Then Hint("由于文件被占用,部分 Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
-        Log("[System] 已确认 " & SuccessfulCount & "/" & McModLoader.Output.Count & " 个 Mod 的状态与要求一致")
-        RefreshList()
+        If Not IsSearching Then
+            '改变禁用数量的显示
+            CType(PanItems.Parent, MyCard).Title = McModGetTitle(McModLoader.Output)
+            '更新加载器状态
+            LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.UpdateOnly)
+        End If
+        If Not IsSuccessful Then
+            Hint("由于文件被占用,部分 Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
+            RefreshList(True)
+        Else
+            ChangeAllSelected(False)
+        End If
+    End Sub
+
+    '删除
+    Private Sub BtnSelectDelete_Click() Handles BtnSelectDelete.Click
+        Dim IsSuccessful As Boolean = True
+        Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown
+        Dim DeleteList As List(Of McMod) = McModLoader.Output.Where(Function(m) SelectedMods.Contains(m.RawFileName)).ToList()
+        For Each ModEntity In DeleteList
+            '删除
+            Try
+                If IsShiftPressed Then
+                    File.Delete(ModEntity.Path)
+                Else
+                    My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
+                End If
+            Catch ex As OperationCanceledException
+                Log(ex, "批量删除 Mod 被主动取消")
+                RefreshList(True)
+                Return
+            Catch ex As Exception
+                Log(ex, $"批量删除 Mod 失败({ModEntity.Path})", LogLevel.Msgbox)
+                IsSuccessful = False
+            End Try
+            '更改 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))
+            Parent.Children.RemoveAt(IndexOfUi)
+        Next
+        If Not IsSearching Then
+            '改变禁用数量的显示
+            CType(PanItems.Parent, MyCard).Title = McModGetTitle(McModLoader.Output)
+            '更新加载器状态
+            LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.UpdateOnly)
+        End If
+        If Not IsSuccessful Then
+            Hint("由于文件被占用,部分 Mod 删除失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
+            RefreshList(True)
+        ElseIf If(IsSearching, PanSearchList, PanItems).Children.Count = 0 Then
+            RefreshList(True)
+        Else
+            ChangeAllSelected(False)
+        End If
+        If IsSuccessful Then
+            If IsShiftPressed Then
+                Hint($"已彻底删除 {DeleteList.Count} 个文件!", HintType.Finish)
+            Else
+                Hint($"已将 {DeleteList.Count} 个文件删除到回收站!", HintType.Finish)
+            End If
+        End If
+    End Sub
+
+    '取消选择
+    Private Sub BtnSelectCancel_Click() Handles BtnSelectCancel.Click
+        ChangeAllSelected(False)
     End Sub
 
 #End Region
 
 #Region "单个 Mod 项"
 
-    '点击:切换可用状态 / 显示错误原因
-    Public Sub Item_Click(sender As MyListItem, e As EventArgs)
+    '点击 Logo:切换可用状态 / 显示错误原因
+    Public Sub ItemLogo_Click(sender As MyListItem, e As EventArgs)
         Try
 
             Dim ModEntity As McMod = sender.Tag
@@ -291,12 +494,13 @@
             Parent.Children.RemoveAt(IndexOfUi)
             Parent.Children.Insert(IndexOfUi, McModListItem(NewModEntity))
             '仅在非搜索页面才执行,以确保搜索页内外显示一致
-            If String.IsNullOrWhiteSpace(SearchBox.Text) Then
+            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 失败")
@@ -358,27 +562,30 @@
             Dim ListItem As MyListItem
             ListItem = If(TypeOf sender Is MyIconButton, sender.Tag, sender)
             Dim ModEntity As McMod = ListItem.Tag
+            '加载失败信息
+            If ModEntity.State = McMod.McModState.Unavaliable Then
+                MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & GetExceptionDetail(ModEntity.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)
-            If ModeDebug Then
-                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(vbCrLf & "—————— 调试信息 ——————")
-                    ContentLines.AddRange(DebugInfo)
-                End If
+            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(" ", "+")
@@ -426,22 +633,14 @@
 
 #End Region
 
+    Public ReadOnly Property IsSearching As Boolean
+        Get
+            Return Not String.IsNullOrWhiteSpace(SearchBox.Text)
+        End Get
+    End Property
     Public Sub SearchRun() Handles SearchBox.TextChanged
-        If String.IsNullOrWhiteSpace(SearchBox.Text) Then
-            '隐藏
-            LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.RunOnUpdated)
-            AniStart({
-                     AaOpacity(PanSearch, -PanSearch.Opacity, 100),
-                     AaCode(Sub()
-                                PanSearch.Height = 0
-                                PanSearch.Visibility = Visibility.Collapsed
-                                PanList.Visibility = Visibility.Visible
-                                PanManage.Visibility = Visibility.Visible
-                            End Sub,, True),
-                     AaOpacity(PanList, 1 - PanList.Opacity, 150, 30),
-                     AaOpacity(PanManage, 1 - PanManage.Opacity, 150, 30)
-                }, "FrmVersionMod Search Switch", True)
-        Else
+        ChangeAllSelected(False)
+        If IsSearching Then
             '构造请求
             Dim QueryList As New List(Of SearchEntry(Of McMod))
             For Each Entry As McMod In McModLoader.Output
@@ -472,15 +671,25 @@
             '显示
             AniStart({
                      AaOpacity(PanList, -PanList.Opacity, 100),
-                     AaOpacity(PanManage, -PanManage.Opacity, 100),
                      AaCode(Sub()
                                 PanList.Visibility = Visibility.Collapsed
-                                PanManage.Visibility = Visibility.Collapsed
                                 PanSearch.Visibility = Visibility.Visible
                                 PanSearch.TriggerForceResize()
                             End Sub,, True),
                      AaOpacity(PanSearch, 1 - PanSearch.Opacity, 200, 60)
                 }, "FrmVersionMod Search Switch", True)
+        Else
+            '隐藏
+            LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.RunOnUpdated)
+            AniStart({
+                     AaOpacity(PanSearch, -PanSearch.Opacity, 100),
+                     AaCode(Sub()
+                                PanSearch.Height = 0
+                                PanSearch.Visibility = Visibility.Collapsed
+                                PanList.Visibility = Visibility.Visible
+                            End Sub,, True),
+                     AaOpacity(PanList, 1 - PanList.Opacity, 150, 30)
+                }, "FrmVersionMod Search Switch", True)
         End If
     End Sub
 

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

@@ -7,8 +7,8 @@
              d:DesignWidth="800" d:DesignHeight="800">
     <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
         <StackPanel x:Name="PanMain" Margin="25,10">
-          <local:MyHint Text="在这个页面修改的设置均只对当前版本生效,这对整合包制作应该会有所帮助。\n你也可以在设置页面中,修改对全部版本生效的全局设置。" IsWarn="False" Margin="0,0,0,10" />
-            <local:MyCard x:Name="CardArgument" Margin="0,15" Title="启动选项">
+            <local:MyHint Text="本页面的设置均只对当前版本生效,这对整合包制作应该会有所帮助。&#xa;你也可以在设置页面中修改对全部版本生效的全局设置。" IsWarn="False" Margin="0,15" />
+            <local:MyCard x:Name="CardArgument" Margin="0,0,0,15" Title="启动选项">
                 <StackPanel Margin="25,40,25,21">
                     <Grid>
                         <Grid.ColumnDefinitions>
@@ -215,4 +215,4 @@
             </local:MyCard>
         </StackPanel>
     </local:MyScrollViewer>
-</local:MyPageRight>
+</local:MyPageRight>

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

@@ -182,8 +182,12 @@
     <Compile Include="Controls\MyCard.vb" />
     <Compile Include="Controls\MyPageRight.vb" />
     <Compile Include="Controls\MyPageLeft.vb" />
+    <Compile Include="Controls\MyIconTextButton.xaml.vb">
+      <DependentUpon>MyIconTextButton.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Modules\Minecraft\ModComp.vb" />
     <Compile Include="Modules\Minecraft\ModJava.vb" />
+    <Compile Include="Modules\Minecraft\ModMod.vb" />
     <Compile Include="Modules\Minecraft\ModModpack.vb" />
     <Compile Include="Modules\ModEvent.vb" />
     <Compile Include="Controls\MyExtraButton.xaml.vb">
@@ -406,6 +410,10 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="Controls\MyIconTextButton.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="Controls\MySearchBox.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>

+ 20 - 12
Plain Craft Launcher 2/Resources/Custom.xaml

@@ -103,26 +103,34 @@
 </local:MyCard>
 
 <local:MyCard Title="事件 4:其他事件类型" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
-        <TextBlock TextWrapping="Wrap" Margin="0,4,0,6" FontWeight="Bold"
+	<StackPanel Margin="25,40,23,15">
+		<TextBlock Margin="0,4,0,6" FontWeight="Bold"
                     Text="EventType 共有以下种类:" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+		<TextBlock Margin="0,0,0,4"
                     Text="· 打开网页、启动游戏、打开文件:已在上方的卡片中介绍" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
-                    Text="· 今日人品、清理垃圾、内存优化:触发百宝箱中的对应功能" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+                    Text="查看 PCL 源代码" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2/blob/main/Plain%20Craft%20Launcher%202/FormMain.xaml.vb"/>
+		<TextBlock Margin="0,0,0,4"
+                    Text="· 今日人品、清理垃圾、内存优化、安装整合包:触发对应功能" />
+		<TextBlock Margin="0,0,0,4"
                     Text="· 复制文本:将 EventData 中的内容复制到剪贴板" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+		<TextBlock Margin="0,0,0,4"
                     Text="· 刷新主页:点击重新加载主页。如果主页文件为联网更新,这会重新下载主页内容" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+		<TextBlock Margin="0,0,0,4"
                     Text="· 下载文件:在点击时要求选择文件保存位置,并且下载 EventData 的链接中的文件" />
-        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
                     Text="下载 MCBBS 的 Logo" EventType="下载文件" EventData="https://www.mcbbs.net/template/mcbbs/image/logo_sc.png" />
-        <TextBlock TextWrapping="Wrap" Margin="0,4,0,0"
+		<TextBlock Margin="0,4,0,4"
                     Text="· 打开帮助:跳转到 EventData 对应的帮助文件(输入相对 Help 文件夹的路径)"/>
-        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
                     Text="打开自定义帮助页面" EventType="打开帮助" EventData="帮助/自定义帮助.json"/>
-    </StackPanel>
+		<TextBlock Margin="0,4,0,4"
+                    Text="· 切换页面:强制切换到 PCL 的某一页面(尝试切换到不太对头的页面会出现不可预知的 Bug,使用时请谨慎)&#xa;       EventData 为 页面种类|次要页面种类(PageType|PageSubType,例如 1|1)&#xa;       点击上方的 查看 PCL 源代码 按钮,在 1000 行左右的位置即可查找到对应数值"/>
+		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+                    Text="切换到下载页面" EventType="切换页面" EventData="1|1"/>
+		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+                    Text="切换到启动器设置" EventType="切换页面" EventData="3|2"/>
+	</StackPanel>
 </local:MyCard>
 
 <local:MyCard Title="横向布局 1:StackPanel" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">

BIN
Plain Craft Launcher 2/Resources/Help.zip


+ 824 - 93
Plain Craft Launcher 2/Resources/ModData.txt

@@ -1,6 +1,6 @@
 
 industrial-craft|工业时代2 (Industrial Craft 2)|515771
-
+@redpower2-core|红石力量2 (RedPower2)|71843
 buildcraft@|建筑 (BuildCraft)|884720
 forestry@|林业*|708812
 railcraft|铁路*|603270
@@ -17,7 +17,7 @@ powerconverters-portablejims-fork|能源转换 (PowerConverters)
 
 modular-forcefield-system|模块化力场系统*
 
-iron-chests|更多箱子* - Forge|372723¨iron-chests-for-fabric@cyberanner-ironchest|更多箱子 (Iron Chests)|372723¨iron-chests-fabric@|更多箱子 (Iron Chests)
+iron-chests-fabric@|更多箱子 (Iron Chests)¨iron-chests|更多箱子* - Forge|372723¨iron-chests-for-fabric@cyberanner-ironchest|更多箱子 (Iron Chests)|372723
 balkons-weaponmod|更多武器 (Balkon's WeaponMod)
 advanced-machines|高级机械 (AdvancedMachines)
 advanced-solar-panels|高级太阳能*
@@ -110,7 +110,7 @@ biomes-o-plenty|超多生物群系 (Biomes O' Plenty)|814732
 
 enhanced-portals-3|增强传送门3*
 compact-solars|压缩太阳能阵列 (CompactSolars / Compact Solar Arrays)|807551
-bibliocraft|收藏馆 (BiblioCraft)
+bibliocraft|收藏馆 (BiblioCraft)|644088
 natura|自然*
 atomic-science|原子科学*
 
@@ -126,7 +126,7 @@ modular-powersuits|模块化动力套装*
 
 
 
-
+@immibis-microblocks|Immibis的小方块 (Immibis's Microblocks)
 
 
 
@@ -156,12 +156,12 @@ minechess|国际象棋 (MineChess)
 
 
 
-@factorization|因式分解 (Factorization)
+@factorization|工厂化/因式分解 (Factorization)
 
 
 
 
-project-red-base|红石计划 (ProjectRed) - 基础¨project-red-core|红石计划 (ProjectRed)
+project-red-core|红石计划 (ProjectRed)¨project-red-base|红石计划 (ProjectRed) - 基础
 
 dimensionaldoors|次元门 (Dimensional Doors)|896027
 chickenchunks|区块载入器 (ChickenChunks)
@@ -206,7 +206,7 @@ highlands|高地*
 doggy-talents|小狗天才*|713458
 plasmacraft|电浆工艺 (PlasmaCraft)
 
-fossils|考古与化石 (Fossils and Archeology Revival)|856874
+fossils@fossils-and-archeology-revival|考古与化石 (Fossils and Archeology Revival)|856874
 glenns-gases|格林的气体 (Glenn's Gases)
 the-lord-of-the-rings-mod-legacy|魔戒:传承 (The Lord of the Rings Mod: Legacy)|363128
 thaumic-tinkerer|神秘工匠*
@@ -214,14 +214,14 @@ thaumic-tinkerer|神秘工匠*
 
 
 xycraft
-chococraft|陆行鸟 (ChocoCraft)
-pams-harvestcraft|潘马斯农场 (Pam's HarvestCraft)|669856
+chococraft@|陆行鸟 (ChocoCraft)
+pams-harvestcraft@|潘马斯农场 (Pam's HarvestCraft)|669856
 
 opis
 
 openblocks|开放式方块 (OpenBlocks)
 
-
+water-power|水力科技*|190676
 
 
 randomcraft-v0-1-0|随机工艺 (RandomCraft)
@@ -256,7 +256,7 @@ gravitation-suite|重力装甲*|734193
 redstone-arsenal|红石兵工厂*¨@redstone-arsenal|红石兵工厂 (Redstone Arsenal)
 big-reactors|大型反应堆*|348989
 
-endertanks|末影水槽 (Ender Tanks)
+endertanks|末影水槽 (Ender Tanks) - Forge¨endertanks-fabric|末影水槽 (Ender Tanks) - Fabric
 applied-energistics-2@ae2|应用能源2*|798231
 hopper-ducts|漏斗管道*
 autofish|自动钓鱼*|630098
@@ -267,7 +267,7 @@ wireless-redstone-cbe|无线红石 (Wireless Redstone)|621428¨wr-cbe-universal|
 per-fabrica-ad-astra-geologica|自合成至星辰:真实地理 (Per Fabrica Ad Astra: Geologica)
 
 redstone-furnace|红石炉*
-pneumaticcraft|气动工艺 (PneumaticCraft: Repressurized) - 1.8及更旧版本¨pneumaticcraft-repressurized|气动工艺 (PneumaticCraft: Repressurized) - 1.12及更新版本¨@pneumaticcraft-repressurized|气动工艺 (PneumaticCraft: Repressurized)
+pneumaticcraft-repressurized|气动工艺 (PneumaticCraft: Repressurized)¨@pneumaticcraft-repressurized|气动工艺 (PneumaticCraft: Repressurized)
 
 
 tubes|管道 (Tubes!)
@@ -283,7 +283,7 @@ project-bench|高级工作台*
 
 apples-plus|更多苹果 (Apples+)
 
-qcraft|量子物理 (QCraft)
+
 
 mad-science|疯狂科学*
 
@@ -293,7 +293,7 @@ the-erebus|混沌之地*|856742
 emerald-mod|绿宝石Mod*|282677
 lucky-block-official|幸运方块 (Lucky Block)
 dynamic-lights|动态光源*|593066
-yarr-cute-mob-models-remake|怪物娘化 (Yarr Cute Mob Models - Remake)|172434
+
 monster-hunter-frontier-craft|怪物猎人边境工艺*
 zombie-awareness|僵尸意识*
 remote-io|远程接口 (Remote IO)
@@ -358,7 +358,7 @@ falling-meteors-mod|陨石 (Falling Meteors)
 
 
 glacia-dimension|冰河时代 (Glacia)|1028541
-water-power|水力科技*|190676
+
 realtrainmod|真实火车 (RealTrainMod)|781058
 applemilktea2|苹果奶茶2 (AppleMilkTea2)|466114
 
@@ -374,7 +374,7 @@ notenoughkeys|更好的按键控制 (Not Enough Keys)
 
 
 
-
+crafting-pillar-mod|合成柱*
 ultratech|次元科技 (UltraTech)
 
 atomicstrykers-infernal-mobs|稀有精英怪 (AtomicStryker's Infernal Mobs)|592395
@@ -384,7 +384,7 @@ pchan3-steamship-steamboat-pirates|飞艇与汽船 (SteamShip - SteamBoat and Pi
 
 thaumic-energistics|神秘能源*|791638
 
-
+chisel|凿子*|641383
 nuclearcraft-mod|核电工艺 (NuclearCraft)
 opencomputers|开放式电脑 (Open Computers)|618383
 elemental-stones|元素之石*
@@ -445,7 +445,7 @@ buildcraft-additions|建筑扩充*
 bedrockium-mod|基岩工艺*
 blocksteps
 thaumic-horizons|神秘视界*
-modtweaker
+modtweaker@
 lootbags|战利品 (Loot Bags)|720841
 compact-lava-generator|高级地热发电机*|522808
 project-zulu|祖鲁*
@@ -469,7 +469,7 @@ cooking-for-blockheads|懒人厨房 (Cooking for Blockheads) - Forge|545680¨coo
 
 psi@
 good-nights-sleep|夜眠梦境/晚安的梦境 (Good Night's Sleep)|965322
-binnies-mods|宾尼的模组合集 (Binnie's Mods)
+binnies-mods@|宾尼的模组合集 (Binnie's Mods)
 baubles|饰品栏*
 more-swords-mod|更多剑 (MoreSwords)
 thaumic-bases|神秘基础学* - 官方版1.7.10¨thaumic-bases-unofficial|神秘基础学 (Thaumic Bases) - 非官方版1.12.2
@@ -486,7 +486,7 @@ warp-book|传送书*
 ex-compressum|压缩工具*|676165
 simply-jetpacks|简易喷气背包*
 blood-arsenal|血液兵工厂*|958995
-sim-u-kraft|模拟城市 (Sim-U-Kraft)
+
 superheroes-unlimited|超级英雄无限*|693397
 oceancraft-mod|海洋世界 (Oceancraft)
 
@@ -524,16 +524,16 @@ keeping-inventory||592605
 orespawn|矿石菌种 (OreSpawn)|464954
 reliquary-v1-3|圣遗物 (Reliquary Reincarnations (Reliquary) / Xeno's Reliquary) - 1.8.9~最新版¨reliquary|圣遗物 (Reliquary Reincarnations* / Xeno's Reliquary) - 1.5.2和1.7.10
 mo-bends|更多弯曲 (Mo' Bends)|205695
-quark|夸克*|1155355
+quark@|夸克*|1155355
 
-official-divinerpg@divinerpg|神圣RPG (DivineRPG)|1080271
+official-divinerpg|神圣RPG (DivineRPG)|1080271¨@divinerpg|神圣RPG (DivineRPG)|1080271
 harvest-festival|丰收物语*|658968
 tough-as-nails|意志坚定*|849264
 non-update|不再有更新*
 clayium|粘土工业*
 better-storage|更好的储存*
 davincis-vessels|达芬奇之舟/达芬奇的船*|475292
-bspkrscore|bspkrs核心 (bspkrsCore)|430478¨bspkrscore-updated|bspkrs核心 (bspkrsCore)
+bspkrscore-updated|bspkrs核心 (bspkrsCore)¨bspkrscore|bspkrs核心 (bspkrsCore)|430478
 techguns|科技枪*|631578
 Iron-Backpacks|特殊背包*
 openmodularturrets|开放式炮台 (OpenModularTurrets)
@@ -544,7 +544,7 @@ dragonapi
 electricraft|电力工艺 (ElectriCraft)
 candycraft|糖果世界 (CandyCraft)|459723
 
-minefantasy2|我的幻想2 (MineFantasy2)
+
 
 rails-of-war|铁路战争 (Rails of War)
 actually-additions|实用拓展*|841281
@@ -575,11 +575,11 @@ llibrary
 
 
 projecte|等价交换重制版 (ProjectE)|1109380
-@redpower2-core|红石力量2 (RedPower2)|71843
 
+customthings|自定义物品 (CustomThings)
 
 automagy|自动化魔法*
-
+minefantasy2|我的幻想2 (MineFantasy2)
 environmental-tech@|环境科技*|644622
 
 magic-cookies|魔法饼干*|710008
@@ -592,7 +592,7 @@ another-one-bites-the-dust
 
 mine-mine-no-mi@|恶魔果实 (Mine Mine no Mi)|828931
 advanced-rocketry|高级火箭*|671760
-
+vics-modern-warfare-mod|维克的现代战争 (Vic's Modern Warfare Mod)|575936
 chance-cubes|机会方块*
 gun-customization
 endercore@|末影核心 (EnderCore)
@@ -616,15 +616,15 @@ dummycore¨dummycore-unofficial
 extra-utilities|更多实用设备*|868981
 openmodslib|开放式模组库 (OpenModsLib)
 embers|余烬*
-neat|极简血量显示* - Forge|938958¨neat-fabric-quilt|极简血量显示 (Neat) - fabric和quilt版本|938958¨@neat|极简血量显示 (Neat)|938958
+neat|极简血量显示* - Forge|938958¨neat-fabric-quilt|极简血量显示 (Neat) - Fabric & Quilt|938958¨@neat|极简血量显示 (Neat)|938958
 mob-properties|怪物属性*
-ganys-surface|Gany的世界 (Gany's Surface)|450171
 
-ezstorage|EZ存储 (EZStorage)
 
+ezstorage|EZ存储 (EZStorage)
+ichunutil
 intangible
 
-forge-multipart-cbe|CB Multipart/Forge Multipart CBE/ForgeMultipart - 1.10.2及以上版本¨forge-multi-part|CB Multipart/Forge Multipart CBE/ForgeMultipart - 1.7.10及以下版本
+forge-multipart-cbe|CB Multipart / Forge Multipart CBE / ForgeMultipart - 1.10.2及以上版本¨forge-multi-part|CB Multipart / Forge Multipart CBE / ForgeMultipart - 1.7.10及以下版本
 the-titans-mod|泰坦生物 (The Titans)|730072
 enchiridion
 stimmedcow-nomorerecipeconflict|合成冲突消除 (NoMoreRecipeConflict)|237776
@@ -666,7 +666,7 @@ valkyrielib@
 waila-harvestability||608318
 mixed-magic|混合魔法*
 hwyla||648893
-crafttweaker||769481
+crafttweaker@||769481
 
 real-time-chat-translation-mod|实时聊天翻译*
 
@@ -688,14 +688,14 @@ project-73926|超级合成框架 (Super Crafting Frame)
 akashic-tome|阿卡什宝典*|805606
 morph-o-tool|变形工具 (Morph-o-Tool)
 decocraft@|装饰工艺*
-refined-storage|精致存储*|1303245
+refined-storage@|精致存储*|1303245
 deep-resonance@|深度共振*
 
 
 
 clockwork-phase|时之沙*
 
-autoreglib
+autoreglib@
 roots|根源魔法*
 guide-api|指南 (Guide-Api)
 fraghappy-reborn
@@ -706,13 +706,13 @@ router-reborn|路由重生*
 coroutil
 vanilla-magic|原生魔法*
 taoism|御灵*|668355
-essentialcraft-4-unofficial|源质魔法4非官方版 (EssentialCraft 4 Unofficial)
+
 celestial-craft|天体*
 lost-thaumaturgy|失落神秘 (Pengu's Lost Thaumaturgy)
 compact-machines|更多空间/压缩空间*
 
 ex-nihilo-omnia|无中生有-世间万物*|631637
-last-sword-you-will-ever-need-mo|最终之剑 (The Last Sword You Will Ever Need Mod)
+last-sword-you-will-ever-need-mo|最终之剑 (The Last Sword You Will Ever Need Mod)|399980
 the-last-sword-you-will-ever-need-reboot|最终之剑:重生*
 
 ftb-library-legacy-forge|FTB Library(旧版) (FTB Library (Forge) (Legacy))
@@ -786,7 +786,7 @@ snad|子沙*
 storage-drawers-extras
 solar-flux-reborn|日光通量:重制版*
 simple-void-world|简单虚空世界*|1089311
-shetiphiancore¨shetiphiancore-fabric
+shetiphiancore-fabric
 shadowmc
 scannable|扫描器*|692037
 rebornstorage|存储重置 (Reborn Storage)
@@ -832,7 +832,7 @@ re-zero-kara-hajimeru-isekai-seikatsu|RE:从零开始的异世界 (Re: Zero kara
 minewatch||730508
 thaumcraft-inventory-scanning|神秘时代物品栏扫描*
 cyclic|循环*|648189
-chisel|凿子*|641383
+
 redstoneplusplus|红石++ (Redstone++)
 ctm|连接纹理 (ConnectedTexturesMod)
 simulated-nights|夜晚模拟*
@@ -870,7 +870,7 @@ omnis-core
 auto-paths|自动路径*
 react
 shared-resource-packs|共享资源包*
-i-know-what-im-doing|我知道我在干什么 (I Know What I'm Doing)
+i-know-what-im-doing@|我知道我在干什么 (I Know What I'm Doing)
 Leather-Works|皮革加工厂*
 cyberware|机械改造*|618353
 thermal-cultivation@|热力农业*
@@ -911,9 +911,9 @@ back-to-eco|生态回归 (Back to Eco)
 world-of-warcraft|魔兽世界*
 the-witcher-adventure|猎魔人*
 fancy-battleaxes|华丽战斧*
-zora-no-densha|Zora的电车 (Zora no Densha)|1085883
+
 roguelike-dungeons|冒险地牢*|300785
-first-aid|急救/医疗护理*|850687
+first-aid@firstaid|急救/医疗护理*|850687
 bonfires|篝火*|772983
 immersive-craft@|沉浸工艺*
 aroma1997core
@@ -935,7 +935,7 @@ improved-extraction|矿物获取改进*
 ecology-mod|生态*
 cakeisalie-mod|蛋糕是个谎言 (CakeIsALie)
 ex-nihilo-adscensio|无中生有:Adscensio*
-bookshelf
+bookshelf@bookshelf-lib
 
 random-things|随意作品*|938101
 
@@ -974,8 +974,8 @@ streams|溪流*
 blockcraftery|方块工匠*
 bedrockores|基岩矿簇 (Bedrock Ores)|776822
 chat-bubbles|聊天泡泡*
-refined-storage-addons|精致存储附属*
-foamfix-for-minecraft@foamfix|泡沫修复 (FoamFix)|678442
+refined-storage-addons@|精致存储附属*
+foamfix-optimization-mod@foamfix|泡沫修复 (FoamFix)|678442
 industrial-foregoing|工业先锋*|774112
 expanded-armory|扩展装备*
 voxelmap|体素地图 (VoxelMap)|177583
@@ -1018,7 +1018,7 @@ wrapup
 nuclear-physics
 wither-skeleton-tweaks|凋灵骷髅调整*
 heat-and-climate|热量与气候*
-customthings|自定义物品 (CustomThings)
+
 exgregilo|格雷矿筛 (ExGregilo)
 placebo
 plants
@@ -1104,7 +1104,7 @@ netherutils
 bonsai-trees|盆栽*|785239
 
 
-misty-world|朦胧世界*
+misty-world@|朦胧世界*
 
 
 beta-kathairis|卡塞瑞斯 (Kathairis/Kether) - 1.12.2|865377¨kathairis-alive|卡塞瑞斯 (Kathairis/Kether) - 1.13+|865377
@@ -1149,7 +1149,7 @@ planar-artifice|镜面艺术: 传承 (Planar Artifice: Continuation)
 
 Requiem|安魂曲 (Requiem/Dissolution/Tartaros)|843736
 slashblade-in-tfc|古刀传 (Slash Blade in TFC)|789943
-mlp-mythical-creatures|神话生物 (Mythical Creatures)
+mlp-mythical-creatures-updated-to-1-7-10|神话生物 (Mythical Creatures)
 metamorph|变形*|723273
 reactioncraft-3-rebirth|世界反应3:重置 (Reactioncraft 3: Rebirth)
 pewter|魔法匠魂*
@@ -1160,7 +1160,7 @@ server-properties-for-lan|简单联机 (Server.Properties for LAN)|781904
 jaopcaadditions|JAOPCA附属 (JAOPCAAdditions)
 cloche-call
 mouse-tweaks@|鼠标手势*|69677
-jaopcaagriculture|JAOPCA神秘农业附属 (JAOPCA Agriculture)
+jaopcaagriculture|JAOPCA神秘农业附属 (Just A Resource Generation Compatibility Attempt)
 sky-resources|空岛资源*|817623
 hexxit-gear|Hexxit装备*
 
@@ -1331,7 +1331,7 @@ armoreablemobs|怪物装备自定义 (ArmoreableMobs)
 gem-buffs-infinite-potion-effects|宝石效果/增益宝石 (Gem Buffs)
 
 advanced-inventory
-crafting-pillar-mod|合成柱*
+
 
 pollution-of-the-realms|环境污染/领域的污染 (Pollution of the Realms)|1410539
 
@@ -1615,7 +1615,7 @@ integrated-terminals|集成终端*
 mekanism-tools@|通用机械工具*
 roboticparts|机械改造1.12+ (Robotic Parts)
 rewired
-@biomesyougo|你将去的生物群系 (Oh The Biomes You'll Go)
+oh-the-biomes-youll-go|你将去的生物群系 (Oh The Biomes You'll Go) - Forge¨oh-the-biomes-youll-go-fabric|你将去的生物群系 (Oh The Biomes You'll Go) - Fabric
 vertically-stacked-dimensions|垂直堆叠的世界*
 
 malisisswitches
@@ -1774,12 +1774,12 @@ up-and-down-and-all-around|颠倒世界*
 lost-souls@|失落的灵魂*
 intelligent-energistics|智能能源*
 ender-compass|末影指南针*|785069
-ic2-tweaker
+
 thaumic-gadgets|神秘宝具*
 multi-mine|多人开采*
 extra-foam-for-liteloader|Liteloader修复 (Extra Foam For LiteLoader)|982739
 embersified|余烬能量转换*
-ichunutil
+
 thaumic-grid|神秘网络*
 back-tools|工具后置/后背工具展示*
 mapwriter-2|地图作者2*|680409
@@ -1791,13 +1791,13 @@ dm2|龙骑士2 (Dragon Mounts 2)
 tiquality
 loot-capacitor-tooltips|电容信息显示*|888624
 classic-bars|经典状态条*
-oh-the-biomes-youll-go|你将去的生物群系 (Oh The Biomes You'll Go) - Forge¨oh-the-biomes-youll-go-fabric|你将去的生物群系 (Oh The Biomes You'll Go) - Fabric
+@biomesyougo|你将去的生物群系 (Oh The Biomes You'll Go)
 overground-ores|地表矿石*
 touhou-little-maid|车万女仆*|882845
 
 eureka
 lazy-ae2|懒人AE2 (Lazy AE2)
-libnine
+
 equaldragons
 berries
 infinity-gauntlet-mod|无限手套 (Infinity Gauntlet)
@@ -2101,7 +2101,7 @@ spatialcompat|空间兼容 (SpatialCompat)
 extended-reach
 no-recipe-book|没有配方书*
 darknesslib
-terrafirmacraft|群峦传说:次世代 (TerraFirmaCraft: The Next Generation)|887732¨tfcraft|群峦传说 (TerraFirmaCraft)|887732
+tfcraft|群峦传说 (TerraFirmaCraft)|887732¨terrafirmacraft|群峦传说:次世代 (TerraFirmaCraft: The Next Generation)|887732
 redstone-control|红石控制*
 snow-real-magic|雪!真实的魔法!⛄ (Snow! Real Magic! ⛄) - Forge|871191¨snow-real-magic-fabric|雪!真实的魔法!⛄ (Snow! Real Magic! ⛄) - Fabric|871191¨@snow-real-magic|雪!真实的魔法!⛄ (Snow! Real Magic! ⛄)|871191
 comforts|舒适用品* - Forge / Fabric / Quilt¨comforts-fabric|舒适用品 (Comforts) - Fabric 1.16.5-1.19.2¨@comforts|舒适用品 (Comforts)
@@ -2303,7 +2303,7 @@ amun-ra
 mysterious-mountain-lib|妖怪之山通用库 (MysteriousMountainLib)
 
 
-
+kitchen-mod|厨房 (The Kitchen Mod)|295019
 
 baubles-reborn|饰品栏重置版*
 pams-brewcraft|潘马斯酿造工艺 (Pam's Brewcraft)
@@ -2415,7 +2415,7 @@ chunk-in-a-globe|球内区块*
 dragon-mounts-legacy|龙骑士重置版 (Dragon Mounts: Legacy)
 fruit-trees|果树 🍊 (Fruit Trees 🍊)|976516
 journey-into-the-light-mod|光明之旅·神之本质 (Journey Into the Light · Essence of the Gods)|1128195
-kitchen-mod|厨房 (The Kitchen Mod)|295019
+
 more-dungeons|更多地牢*
 voyage
 idealland|理想境*|1051328
@@ -2464,7 +2464,7 @@ moddirector
 function-api
 targetingapi
 re-targetingapi
-
+libnine
 dominionlib
 
 endergetic|末地拓展 (The Endergetic Expansion)
@@ -2784,7 +2784,7 @@ mekanism-fluxified|“通量”机械*
 stepup|平滑自动上坡 (StepUp)
 @sodium|钠 (Sodium)|1079347¨sodium|钠*|1079347
 
-
+netherlicious||1104913
 yungs-better-mineshafts-forge|YUNG的矿井优化 (YUNG's Better Mineshafts) - Forge¨yungs-better-mineshafts-fabric|YUNG的矿井优化 (YUNG's Better Mineshafts) - Fabric¨@yungs-better-mineshafts|YUNG的矿井优化 (YUNG's Better Mineshafts)
 terrible-chest|可怕的箱子/可怖箱子*|922554
 spartan-shields|斯巴达之盾*|672959
@@ -2856,7 +2856,7 @@ jei-enchantment-info|JEI附魔信息 (JEI Enchantment Info)
 
 project-red-fabrication|红石计划:制作 (Project Red - Fabrication)
 project-red-integration|红石计划:集成 (Project Red - Integration)
-project-red-lighting|红石计划:照明 (Project Red - Lighting)¨project-red-illumination|红石计划:照明 (Project Red - Illumination)
+project-red-illumination|红石计划:照明 (Project Red - Illumination)¨project-red-lighting|红石计划:照明 (Project Red - Lighting)
 project-red-world|红石计划:探索 (Project Red - World)
 project-red-compat|红石计划:兼容性 (Project Red - Compat)
 canvas-renderer|Canvas渲染器*|1211675¨@canvas|Canvas渲染器 (Canvas Renderer)|1211675
@@ -2917,7 +2917,7 @@ upgrade-aquatic|碧海新生*|962459
 tfc-ambiental
 macaws-trapdoors|Macaw的活板门 (Macaw's Trapdoors)
 zenutil
-
+instant-bubble-studio|瞬息气泡工坊*|1103368
 better-beacon-effect|更好的信标效果*
 netherite-horse-armor|下界合金马铠*
 tacocraft-fabric
@@ -2925,7 +2925,7 @@ lootr¨lootr-fabric
 biolib@bio-library
 scavenge¨documentation|Scavenge - WIKI: 模组文档(1.10.2-1.12.2)
 
-
+heaven-earth-ring|乾坤戒指*|1113665
 
 pams-harvestcraft-2-food-extended|潘马斯农场2 - 食物拓展 (Pam's HarvestCraft 2 - Food Extended)
 
@@ -2955,7 +2955,7 @@ lambdynamiclights@|Lambda的动态光源 (LambDynamicLights)|1082100
 realistic-torches|真实火把*|566201
 
 durability-viewer|耐久显示器*|1086168
-netherlicious||1104913
+
 
 
 ground-item-highlighting|地面物品高光显示*|1084931
@@ -2976,7 +2976,7 @@ packagedexcrafting|封包合成拓展 (PackagedExCrafting)
 
 compactstorage|紧凑存储 (compactstorage)
 useful-backpacks|实用背包*
-conquest-reforged|征服者 (ConquestReforged)|1431287
+@conquest-reforged|征服者 (ConquestReforged)|1431287
 steelseries-gamesense
 pizzaatimes-timber-mod
 tellme
@@ -3006,7 +3006,7 @@ server-translations
 image2map@
 corpse@|遗体*
 ring-of-the-miner-fabric|矿工戒指 (Ring of the Miner) - Fabric¨ring-of-the-miner-forge|矿工戒指 (Ring of the Miner) - Forge
-instant-bubble-studio|瞬息气泡工坊*|1103368
+
 
 
 
@@ -3016,7 +3016,7 @@ kaleido
 
 crock-pot|烹饪锅*
 
-heaven-earth-ring|乾坤戒指*|1113665
+
 
 
 
@@ -3059,7 +3059,7 @@ entity-culling|实体渲染机制优化*
 hammerandvil|锻造锤与锤锻台 (HammerAndVil)
 
 simply-backpacks|简易背包*
-mermod-fabric¨mermod-forge|Mermod / Mermaid Tail Mod - 1.19.2~1.18.2 Forge版本
+@mermod
 better-placement|更好的放置*|730136
 fat-cat|大资本家*
 waystones2waypoints|传送石碑路径点 (Waystones2Waypoints)
@@ -3190,7 +3190,7 @@ ambience-extras|环境音乐:附加 (Ambience - Extras)
 additions-mod
 extracpus|更多CPU (Extra CPUs)
 extra-crafting-storage
-@conquest-reforged|征服者 (ConquestReforged)|1431287
+conquest-reforged|征服者 (ConquestReforged)|1431287
 underground-city-engineering@|地下城市工程*|943842
 essentials
 soul-shards-respawn
@@ -3246,7 +3246,7 @@ randompatches-integration
 bubble-column-elevator-backport|1.13水域机制 (1.13 Water Mechanics)
 basic-nether-ores|基础下界矿石*|1136294
 nuclearcraft-reactor-builder|核电工艺反应堆建造机 (NuclearCraft Reactor Builder)
-pams-harvestcraft-2-food-core|潘马斯农场2 - 食物核心 (Pam's HarvestCraft 2 - Food Core)|1313245
+pams-harvestcraft-2-food-core|潘马斯农场2 - 食物核心 (Pam's HarvestCraft 2 - Food Core)|1000336
 eki-lib
 
 nodami|伤害免疫机制移除 (No Damage Immunity)
@@ -3750,14 +3750,14 @@ necromancy
 library-of-exile
 
 variegated
-
+chest-cavity@|胸腔*
 extended-note-block|音符盒扩展 (Extended Noteblocks)
 hbms-nuclear-tech-mod-reloaded|HBM的核科技重制版 (Hbm's Nuclear Tech Mod Reloaded)
 loading-timer@|加载时长显示*
 ocrocketry
 chime-fabric@chime
 witherite@|凋灵合金*
-mermaid-tail-mod|人鱼尾*¨@mermod
+mermod-fabric¨mermaid-tail-mod|人鱼尾*¨mermod-forge|Mermod / Mermaid Tail Mod - 1.19.2~1.18.2 Forge版本
 astromine-core|天体矿工:核心 (Astromine: Core)
 dungeondq
 dynamic-trees-integrated-dynamics|动态的树:动态联合/集成动力附属 (Dynamic Trees - Integrated Dynamics)
@@ -4085,7 +4085,7 @@ ticktock@servertick
 
 fps-reducre-for-fabric|FPS减速器(Fabric) (FPS Reducer (For Fabric))
 art-artillery|艺术?火炮! (Art? Artillery!)
-minemenu-refabricated@minemenufabric|我的菜单 Fabric 版 (MineMenuFabric)
+minemenufabric@|我的菜单 Fabric 版 (MineMenuFabric)
 @tabtps
 @afkpeace¨afkpeace
 
@@ -4112,7 +4112,7 @@ village-employment|村庄就业*
 
 deathlog@|死亡日志*
 automatic-elytra|自动鞘翅*
-
+axolotl-bucket-fix|美西螈桶修复*
 xks-decoration|XK的装饰 (XK's Deco)|1214362
 endgame-materials-addons|更多终局材料*
 ravage-and-cabbage
@@ -4556,7 +4556,7 @@ multi-world
 
 terralith¨@terralith
 overworld-two¨overworld-two-forge
-
+xl-packets|XL数据包 (XL Packets) - Forge¨xl-packets-fabric|XL数据包 (XL Packets) - Fabric
 rftools-storage@|RF工具:存储 (RFTools Storage)
 rftools-builder@|RF工具:建造机 (RFTools Builder)
 
@@ -4724,7 +4724,7 @@ desolation-forge|荒芜 (Desolation) - Forge¨desolation|荒芜* - Fabric
 ma-astral
 zaraklib
 chipped@
-chest-cavity@|胸腔*
+
 mending-for-1-7-10|给老版本的经验修补 (Mending for Older Version)
 unreachable-fantasy-mod
 ai-reducer|AI减速器 (AI Reducer)
@@ -4963,7 +4963,7 @@ one-block-forge
 human-reborn
 @addadd|额外扩展 (Additional Additions)|1427321¨additional-additions|额外扩展* - Fabric|1427321¨additional-additions-forge|额外扩展 (Additional Additions) - Forge|1427321
 earlygame@
-happiness-is-a-warm-gun@
+
 
 kvc-blocks|蛋挞君的原版补全方块篇 (KVCBlocks)|1239534
 enigtech2-util
@@ -5073,7 +5073,7 @@ food-effects
 item-borders|物品边框* - Forge¨item-borders-fabric|物品边框 (Item Borders) - Fabric
 iceberg|冰山* - Forge¨iceberg-fabric|冰山 (Iceberg) - Fabric¨@iceberg|冰山 (Iceberg)
 
-
+worldeditcui-forge-edition-3
 enhanced-farming@|增强农业*
 
 
@@ -5254,7 +5254,7 @@ durability101@
 ceramic-shears|陶瓷剪刀*
 torch-bandolier|火把弹药袋*
 rapid-leaf-decay
-
+netherite-horse-armor-mod|下界合金马铠*
 ambient-environment
 glass-cutter
 dicemc-tiered-armors
@@ -5420,7 +5420,7 @@ compass-manhunt
 exlines-furniture
 tinkers-archery|工匠箭术 (Tinkers' Archery)
 designer-modeling|设计师建模*
-netherite-horse-armor-mod|下界合金马铠*
+
 netherite-horse-armor-fabric|下界合金马铠 (Netherite Horse Armor)
 netherite-elytra|下界合金鞘翅*
 
@@ -5952,7 +5952,7 @@ fabricskyboxes@
 @fvt
 blue_skies_tcon
 tdv-tweaks
-
+ic2-tweaker
 picture-in-picture-pip
 guilt-trip
 parcool|跑酷! (ParCool!)
@@ -6042,7 +6042,7 @@ the-one-probe-fabric
 gehenna-super-spooky-nether-overhaul-for-forge
 draconicminus
 hygiene
-worldeditcui-forge-edition-3
+
 external-view-reload
 fluid-drawers-legacy|储液抽屉:遗产*
 my-server-is-compatible
@@ -6659,7 +6659,7 @@ astemirs-fireflies
 ender-crop¨@ender-crop
 modupdater
 fivehead
-axolotl-bucket-fix|美西螈桶修复*
+
 attained-drops|可种植掉落物*
 attained-drops-2|可种植掉落物2*
 
@@ -7797,7 +7797,7 @@ hungertweaker
 knob-control
 daedalus-labyrinth
 tip-the-gui|界面尺寸设置优化 (GUI Scale)
-copper-overhaul
+
 epic-fight-nbt-integration
 login-protection|登录保护*
 swem
@@ -8252,7 +8252,7 @@ super-secret-settings
 @plumeconfig
 geophilic@
 critter-fights
-hypothermic@
+
 nosecrets|不秘密 (NoSecrets)
 extra-creeper-types
 loot-casket
@@ -8759,7 +8759,7 @@ otg-skylands
 fxs-rail-optimization@rail-optimization
 morejs@
 
-@moresnowballls|更多雪球 (MoreSnowballs)
+
 valkyrien-skies-control
 the-end-expanded-mod-ice-and-fire-addon
 @vault-patcher|保险库补丁 (Vault Patcher)
@@ -8822,7 +8822,7 @@ dawn-to-dusk
 open-tower-defence|开放式塔防*
 mtr-station-decoration@station-decoration
 perviaminvenire
-
+hypothermic@
 silverbirch@silver-birch
 
 
@@ -9078,7 +9078,7 @@ commandspy
 slimyfloor
 daily-dad@|每日冷笑话*¨daily-dad-server@|每日冷笑话 (Daily Dad) - Daily Dad - Server
 good-ending
-xl-packets|XL数据包 (XL Packets) - Forge¨xl-packets-fabric|XL数据包 (XL Packets) - Fabric
+
 creeper-nuggets
 coherent-villages
 
@@ -9204,7 +9204,7 @@ snowballing
 winter-essentials
 magnesium-rubidium-extra-potato-editon|镁/铷附属:土豆版 (Magnesium/Rubidium Extra: Potato Editon)
 @realistic-cruelty|真实残忍 (Realistic Cruelty)
-trapper-pelts
+
 workshops-of-doom
 create-mechanical-spawner|机械动力:动力刷怪笼*
 
@@ -9849,7 +9849,7 @@ enchancement
 
 useful-spyglass@
 @illagers-love-armor
-
+@moresnowballls|更多雪球 (MoreSnowballs)
 befriendmobs-days-with-monster-girls|友好化生物:与怪物娘的日常 (BefriendMobs: Days with Monster Girls)
 launchgui
 villager-armor-layer@|村民盔甲显示*
@@ -9866,7 +9866,7 @@ extra-trades
 blood-particles-mod
 @two-weeks-hud
 fazcraft-mod
-mod-menu-no-more@changelog
+mod-menu-no-more@
 @slime-craft
 way2wayfabric@
 cloche-profit-peripheral
@@ -9960,7 +9960,7 @@ enderio-unofficial|末影接口非官方版 (EnderIO Unofficial)
 
 dimensional-gravity
 
-
+happiness-is-a-warm-gun@
 bring-color-to-my-skies
 portal-dungeons
 
@@ -10217,7 +10217,7 @@ boat-break-fix
 advanced-movement
 totem-overhaul
 mobs-no-render@
-
+copper-overhaul
 covenants-choose-your-purpose
 insatiable
 
@@ -10466,4 +10466,735 @@ chunk-sending-forge-fabric
 overworld-mirror
 @toggleable-sonic-boom
 quicksort@
-fake-player@fake-players
+fake-player@fake-players
+@lootbeams-fabric|战利品光束Fabric版 (LootBeamsFabric)
+continuumation
+ai-art-deco-1|AI装饰一号 (AI Art Deco 1)
+@occlusion-culling-fix-sodium
+easy-dungeons|简易地牢*
+the-morbid-harvester-reborn-reborn|变态的凋灵收割者 (The Morbid Reborn Reborn)
+ars-armiger
+bettergolem
+@end-poem-extension|终末之诗扩展补丁 (End Poem Extension)|1435381
+trampledisabler-fabric
+
+advanced-tfc-tech-unofficial|进阶群峦科技非官方版 (Advanced TFC Tech Unofficial)
+
+impostore
+effishiency
+glumbis
+prehistoric-nature-fossils
+peeled
+butterflys
+@forgedapi
+
+be-style-wither@
+campfire
+ars-enderstorage
+@ftbchecker
+mythicmetals-decorations
+metallurgychisel
+sooty-chimneys-fabric@
+
+mekanism-evolution
+
+chiseledadditions
+
+gregtech-4|格雷科技4移植版 (GregTech 4 Rewritten)
+lacrimis
+productive-villagers
+canned-goods@
+itemchat-fabric@itemchat
+
+crafttweaker-sixik-utils-ftb-quest
+wares@
+
+recoil-shotgun@
+
+observed@
+ex-sartagine-requiem@
+giant-pacman-1-16-5
+item-banning-fabric
+erroring-entity-remover
+mclo-gs
+pink-expansion
+polluted-earth-reborn
+kitsus-forgecraft
+stat-check
+hit-indication@
+multipart-machines-farming
+
+lotrfixer
+tru-first-person-mod
+reduced-music-delay
+agers-giant-junkyard-dimension
+jsmacros
+great-big-world
+armored-mobs
+inversia@
+end-expansion-the-lamented-islands
+
+
+
+@compromise|折衷 (Compromise)
+
+no-additional-repair-cost
+cave-dust@|洞穴尘埃*
+
+
+
+create-applied-kinetics|机械动力:应用机械 (Create: Applied Kinetics)
+nocubes-zombie-mobs
+create-gourmet
+medical
+yellowbrosss-extras
+@simply-caterpillar
+cataclysm-delight
+old-combat-mod@
+pipe-connector@
+diamethyst-arrows@
+cherry-blossom-grotto
+
+
+parties
+pots-and-mimics-rpg
+@gemspark
+otakomod
+wall-jumping
+new-life
+reeves-furniture
+@undying|不朽 (Undying)
+creatania
+boat-rocked
+proficiency-honest-work
+warrior-rage@
+
+pokefood
+armor-souls-reforged
+
+@the-aether-redux|天境:新生代 (The Aether: Redux)
+@banner||1447687
+staaaaaaaaaaaack@
+thanks-i-have-rei@tihr
+
+satchels|挎包*
+matex
+weapons-of-the-apocalypse
+tis-advanced@
+
+toroquest-revamped
+
+tis-create
+tektopia-trader
+@worldtime
+beasts
+alphabet-speedrun|自定义速通挑战 (αβspeedrun)|1441945¨@alphabet-speedrun|自定义速通挑战 (αβspeedrun)|1441945
+@symlink-check|符号链接拦截 (Symlink Check)|1446393
+tis-stringify
+
+@dismount-entity
+dyeable-compasses@|染色指南针*
+supercompression|超级压缩 (Super Compression)
+better-hungteen-s-plants-vs-zombies|更好的HTPVZ (bhtpvz)
+piglinsafety|猪灵:多喝岩浆 (PiglinSafety)
+gatewayer
+@bocchud|孤独HUD (BoccHUD/MiniHUD-Forge)
+arrowcollector|你返箭吗 (Arrow Collector)
+saplingslayer|鸽子衔枝 (SaplingSlayer)
+quality-quails
+hydration
+
+halohud@
+@resourcify
+ioclias@
+twilights-flavors-delight@twilight-delight|暮色风味乐事 (Twilight Flavors & Delight)
+@clean-f3
+
+architect-tools-converter-1-7-10|建筑师工具 - 转换器 (Architect Tools - Converter)
+
+@librarian-trade-finder
+server-resourcepack-checker@server-resource-pack-checker
+main-menu-credits@
+@photon-editor|光子 (Photon)
+@voice-chat-interaction
+replay-voice-chat@
+ships-mod-unofficial|船:非官方版 (Cuchaz Ships Unofficial)
+
+storage-overhaul
+chorus-warps
+aureate-reliquary
+submerged-explosions
+netherite-items
+aim-plus
+reckless-drilling
+tick-stasis@
+@azure-paxels
+
+no-angled-brackets
+alien-apocalypse
+server-utilities
+su-addon-silk-spawners
+su-addon-quests
+harder-hardcore
+argentinasdelight
+colonies
+@everythingcopper|万物皆铜 (Everything is Copper)|1447405
+passive-endermen@|被动型末影人*
+
+elemental-aspects
+dune
+lithereal
+mobs-genus
+ore-extension
+extradelight
+shady-alchemist
+ender-knight
+bubbleland
+cake-cow
+
+shrimps
+projecte-aether-addon
+coal-additions
+fungi-stew
+grenade-launcher
+illness
+structural-statues
+
+wild-bushes
+mutant-wolf
+curses-naturals
+
+straighten-up@
+twilight-catalist|暮色催化石*
+extra-moas
+corruptional
+@enablemultiplayermode
+wizardy-delight
+
+
+@tinyallies
+all-da-nuggets
+random-recipes
+starks|史塔克一家*
+easy-paper
+modern-ruins-pack
+tetras-delight
+
+avaritia-reset-version|无尽贪婪重置版*
+archeology-plus@
+mobification
+trimseffects
+tough-glass@
+celestisynth|天神剑技*
+
+more-axolotls@more-axolotl
+@perfect-accuracy
+@perfect-accuracy-forge
+@swingthrough
+log-begone@
+@transferable-pets
+@bone-blocks
+woodster@
+deaths-mark
+sweet-boreal@
+fuzzymobs
+@beslandia
+@ars-magica-25|魔法艺术2.5 (Ars Magica 2.5)
+delightful-froge
+
+new-world-height-and-depth
+@inertiaanticheat
+
+scroll-of-harvests
+@scripts-chunk-loaders
+clean-tooltips@
+
+quick-save@quicksave|快速存档&快速读档 (QSave&QLoad)|1448317
+iron-bows-forge¨iron-bows-fabric
+
+filtered-chests
+
+
+@anti-xray
+special-strength
+armorpoints@
+easy-enchanting@|轻松附魔*
+elitia
+cosmetic-wings||385564
+
+@death-backup
+bear-with-me
+create-diesel-generators
+vintagefix
+
+nostalgic-tweaks@
+suspicious-sand-maker
+create-cobblemon-industrialized
+create-pixelfactory
+ies-tweaks
+@haunt-furnace
+player-ladder
+orb-of-origin-plus
+
+firework-minecart-mod@
+soots-sandwichcraft
+rpg-origins
+@zombie-lag-fix
+classic-minecraft-icon@
+@the-binding-of-isaac-useful-items
+@trivia
+some-useful-ores
+@stardew-valley-mod
+rainy-harvest
+mythic-mobs@
+lost-features@lostfeatures
+bagus-lib@
+
+@better-doors
+@server-pack-unlocker¨3178320¨3178321
+x-enchant
+
+@blahaj-remake
+pamps-simple-edition
+@babyzombie-drops
+
+better-chiseled-bookshelves|更好的雕纹书架*
+@shorks
+@missing-vanilla-recipes
+alcocraft
+
+
+mowlib
+
+
+not-just-biomes@
+agricultural-enhancements|农业增强*
+
+
+
+ramel@
+
+@chat-tools
+amys-rftl
+cutepenguins|可爱的企鹅 (Cute Penguins)
+hellions-sniffer@hellions-sniffer+
+
+hybrid-flowers|杂交花*
+
+
+@methane
+mushroom-villagers
+@litematica-server-paster
+s33r-butterfly-paintings
+@more-statistics
+villager-see-villager-do¨@villager-see-villager-do
+
+silent-river-in-the-nether|炎液断蒸*
+
+sulfar-mod
+apple-cows
+gardening-tools
+nether-ores-plus
+river-treasures
+lumbers-axe
+
+immunity-enchantments
+better-fishing-rods
+book-fishing
+more-villager-trades
+custom-world-icons@
+trims-begone
+haste-enchantment
+workshop-for-handsome-adventurer
+desert-upgrade
+ocean-recovery
+more-armor-trims
+all-the-trims@allthetrims
+@bamboo-sign-editor|告示牌编辑器 (Bamboo Sign Editor)
+revival-orb@
+stackable-trims
+keepcuriosinventory|饰品栏优化 (KeepCuriosInventory)
+megachicken
+s33r-iris-azalea-mod
+difficultyplus
+black-lung¨@blacklung
+friends-and-foes-beekeeper-hut-forge¨friends-and-foes-beekeeper-hut-fabric¨friends-and-foes-beekeeper-hut-quilt
+friends-and-foes-flowery-mooblooms-forge¨friends-and-foes-flowery-mooblooms-fabric¨friends-and-foes-flowery-mooblooms-quilt
+@random-looting|随机战利品表 (Random Looting)
+kubejs-botany-pots@
+irons-rpg-tweaks
+@enchantips
+tokenable-decoration
+@emogg
+@festival-delicacies|节日佳肴 (Festival Delicacies)
+elytra-trims@
+
+gothic-rpg
+@arcadian-dream|幻想之梦 (Arcadian Dream)
+
+dungeon-and-taverns
+elytra-trims-extensions@
+fallingblocks
+fabric-musica
+growmeal
+moa-decor-art
+
+
+easier-armor-trim-duplication
+steppy@
+new-thin-air|稀薄的空气 (Thin Air)
+the-armory
+xannoszs-better-minecarts|Xannosz的更好的矿车 (Xannosz's Better Minecarts)
+meliusvanish@vanish
+tooltip-tool-tips@
+sanity-descent-into-madness@
+@no-darkness-effect
+@seamless-loading-screen-renewed
+armorskin@
+@translucent-glass
+simple-health-bar@
+plenty-of-golems
+@hit_particles
+@durability-viewer-updated
+puffish-skills@skills
+better-archeology
+
+apoca-mobs
+
+@snow-sniffer
+responsive-shields@responsiveshields
+beehave@|蜜蜂信息显示*
+
+sign-clipboard@
+nocubes-better-frogs
+@vanilla-permissions
+create-dreams-desires@create-dreams-and-desires
+trails-and-tales@trails-and-tales-plus
+
+stackable127@
+
+
+wanderer-profession
+
+cataclysmic-combat
+persona
+
+create-air-forge
+@gravity-pads
+
+
+@inhibited
+
+sekc-physics¨sekc-physics-fabric
+@artificial-lightning-mod
+do-api@
+@pythonmc
+@tp-chat-cmd|TP 聊天命令 (TP Chat Command)
+grabby-mobs@
+pandas-falling-trees@
+iron-ender-chests
+@delightdulcreators
+
+
+
+dusty-decorations@
+simple-double-jump
+
+save-the-monument
+snifferiety@
+light-the-night
+
+terrafirmaships
+better-ocelots
+
+denyblocks
+sculk-horn@-sculk-horn
+ydms-red-panda
+overworld-piglins
+waveapi
+modlist
+panorama-screens@
+campfire-spawn-and-tweaks
+ghostz@
+better-totem-of-undying¨better-totem-of-undying-fabric¨@better-totem-of-undying
+modonomicon@
+moreyoyos
+effortless-fabric
+onetwenty-1-20-backport@trailstales-backport
+xtra-arrows
+secrets-of-forging-revelations-a-tetra-addon
+trolldom
+@made-in-china|中国制造 (Made In China)
+soundlimitextends
+artemis-flower-pots
+sculky-bits
+minecolonies-for-computercraft
+flame-sweeping-potato-edition|烈焰横扫:土豆版*
+skinned-carts
+quick-effect@quickeffect
+block-log
+
+
+@gms|游戏模式选择器 (GameModeSwitcher)
+dimension-localized-inventories
+skillcraft-modpack-tool
+
+
+@notsoessential
+worldsalads-opulence
+
+@wyrms
+pearfection
+enderchests¨enderchests-fabric
+dragon-rider
+create-utilities-fabric
+
+creeperhost-presents-chickens
+inet@
+
+my-beloved-icon@
+
+tetra-enlarged
+fruit-delight@|果品乐事*
+@idletweaks
+tagmod
+passive-skill-tree
+villagertradefix
+
+
+
+fishermans-trap
+@removebats|禁止蝙蝠生成 (RemoveBats)
+nabba
+enchantdemon|附恶魔 (EnchantDemon)
+
+
+create-alchemists-industry@
+
+
+
+
+
+@modern-sound
+@polished-gui
+all-bark-all-bite
+lively-forests
+
+dynamic-fullbright@
+@modularwarfare-shining|模块化武装:烨熠 (ModularWarfare-Shining)
+@dcch
+difficulty-locker
+virus-disease-mod
+this-boat-is-mine@|这是我的船! (This boat is MINE!)
+pandas-extra-details@
+ebs-wildfire
+
+
+bobby-forge
+matter-transporter
+tall-flower-pots
+
+too-fast
+
+fast-paintings
+personality-rp@personality
+
+
+@fabric-yaml-configuration
+comfy-beds
+twerk-crop-dusting-mod-fabric
+extra-damage-enchantments@
+lemon-core
+rightclickharvest
+planters-forge
+passive-charms
+
+
+@linkart
+@jamlib
+globalization
+renewable-spore-blossoms
+@paragraphs
+
+@scoreboard-helper|记分板助手 (Scoreboard Helper)
+
+
+
+
+
+
+
+
+
+
+mobs-info@mobsinfo
+@ngrok-api
+
+
+@swordunsheather|利刃出鞘:重铸 (Sword Unsheathe Recasting)
+rpgloot
+@sus-saver
+
+dynamic-fps-reforged@
+utility-belt@
+cultivatecraft
+rpgloot-patched
+fluidlogged
+create-foundry
+@cobblemon-fight-or-flight
+trapper-pelts@
+ender-magnet|末影磁铁*
+dead-guys-hallucination-nightmare
+
+
+simple-hot-air-balloons
+@toomuchrain
+thirst-was-remade@|口渴-重制*
+ies-tweaks-fabric@
+brown-mooshrooms
+@whetstones
+@better-log4j-config
+@harvest
+wooden-hoppers
+skeleton-horse-spawn
+configanytime
+faster-crouching
+yeetusexperimentus
+@xbob
+zombie-horse-spawn
+tinkers-katanas
+fbp
+@yeetem-potions
+incapacitated¨@incapacitated
+@fys|原版注册解封 (Forge Yourself)
+@mcopper|梧桐物语 (MCopper)
+no-ranged-knockback
+industrial-agriculture
+macaws-bridges-sajevius|Macaw的桥梁:Sajevius 附属 (Macaw's Bridges - Sajevius)
+@getwebbed
+@water-resistance-potion
+the-predators
+create-structures
+ivycore@
+more-immersive-wires
+ventur-origin@venturorigin
+
+@sodium-blendingregistry
+waterphysics
+ece-enhanced-celestial-enhancement|月亮事件扩展:第二月亮 (ECE - Enhanced Celestial Enhancement: The Second Moon)
+plants-vs-zombies-cubed@pvzcubed
+
+
+
+
+create-guardian-beam-defense
+antisocial@
+expanding-technologies@
+despawning-eggs-hatch
+mob-death-messages
+chunkapi@
+@betterwaystonesmenu
+@big-fish
+@nulltal
+@caffeinefilled
+pet-transfer-trade-your-pets@pet-pass-trade-your-pets!
+lets-do-bakery
+searchables@
+dixtas-armory@
+@ingamestats
+hanger-system-overhaul
+@isaidnosnow
+jarsauth|客户端验证 (JarsAuth)
+autoelytrapanic¨@autoelytrapanic
+more-crossbows-cj
+multipart-machines-cooking
+
+
+thepeebrain-dream-dimension
+
+@ochetgenyo
+@qcraft|量子物理重构 (qCraft Reimagined)
+
+@todo-list|待办事项 (TODO List)
+@arst-armor-hud
+@spice-of-life-onion|生活调味料:洋葱版 (Spice of Life Onion)
+effect-berries@
+tinkersworld
+
+
+ragdoll-corpses|布娃娃尸体*|431874
+floatingruins|浮空遗迹 (FloatingRuins)|301900
+
+creeper-backguard@creeperbackguard
+audio-extension-for-fancymenu-forge
+stay-warm-together
+aeroblender
+ancient-aether
+artisticcraft@
+lavaboat_mod|熔岩船 (LavaBoat)
+sophisticated-wolves|精致的狼 (Sophisticated wolves)
+enderio-alloys
+dynamic-music
+@item-hunt
+dynamo
+create-sabers
+
+split-shulker-boxes@
+tinkers-leveling
+curios-compat
+@cloudbackup|云备份 (CloudBackup)
+@serverpingerfixer
+@world-days
+such-a-delight
+create-compressed
+emoji-type@
+
+tower-defense-units
+barrier-block-mod|屏障方块*
+@simplehealthorbs
+misctweaks_
+dynamic-music-updated@
+spartan-netherite
+climb-ladders-fast
+@custom-machinery-mekanism#all-versions
+
+
+
+@noraidtotems
+@snowballs_plus
+bagus-mob
+chat-twix@
+shulker-meat
+accurate-block-placement-reborn
+favorite-items
+
+@smart-villagers-follow-emeralds|见钱眼开 (Smart Villagers Follow Emeralds)
+
+@villager-pickup
+step-it-up
+fantasy-trees|奇幻树*
+
+remodified|你再造! (Remodifier)
+
+
+@better-fusion-reactor-for-mekanism
+@growableores
+golemsarefriends
+
+
+@keepcommandhistory
+
+
+generatorgalore
+@map-shirts
+
+dyenamicsandfriends
+
+
+fusion-connected-textures@
+bluebox
+hunger-reworked
+armor-status-hud-renewed|耐久信息显示:重生 (Armor Status HUD Renewed)
+boblib
+placeable-mobs
+potion-blender