龙腾猫跃 %!s(int64=3) %!d(string=hai) anos
pai
achega
e6c6975047
Modificáronse 41 ficheiros con 2616 adicións e 1320 borrados
  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=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, "内存优化失败")
                         MsgBox(ex.Message, MsgBoxStyle.Critical, "内存优化失败")
                         Environment.Exit(-1)
                         Environment.Exit(-1)
                     End Try
                     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
 #If DEBUG Then
                 ElseIf e.Args(0) = "--make" Then
                 ElseIf e.Args(0) = "--make" Then
                     '制作更新包
                     '制作更新包
@@ -71,7 +74,8 @@ Public Class Application
             Directory.CreateDirectory(PathAppdata)
             Directory.CreateDirectory(PathAppdata)
             '检测单例
             '检测单例
 #If Not DEBUG Then
 #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
             If WindowHwnd <> IntPtr.Zero Then
                 '将已有的 PCL 窗口拖出来
                 '将已有的 PCL 窗口拖出来
                 ShowWindowToTop(WindowHwnd)
                 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
     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
             End If
-            ModEvent.TryStartEvent(EventType, EventData)
         End If
         End If
+        ModEvent.TryStartEvent(EventType, EventData)
     End Sub
     End Sub
     Public Property EventType As String
     Public Property EventType As String
         Get
         Get
             Return GetValue(EventTypeProperty)
             Return GetValue(EventTypeProperty)
         End Get
         End Get
-
-        Set(ByVal value As String)
+        Set(value As String)
             SetValue(EventTypeProperty, value)
             SetValue(EventTypeProperty, value)
         End Set
         End Set
     End Property
     End Property
@@ -159,8 +157,7 @@
         Get
         Get
             Return GetValue(EventDataProperty)
             Return GetValue(EventDataProperty)
         End Get
         End Get
-
-        Set(ByVal value As String)
+        Set(value As String)
             SetValue(EventDataProperty, value)
             SetValue(EventDataProperty, value)
         End Set
         End Set
     End Property
     End Property

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

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

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

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

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

@@ -46,7 +46,7 @@ Public Class MyLoading
         Get
         Get
             Return _Text
             Return _Text
         End Get
         End Get
-        Set(ByVal value As String)
+        Set(value As String)
             _Text = value
             _Text = value
             RefreshText()
             RefreshText()
         End Set
         End Set
@@ -57,7 +57,7 @@ Public Class MyLoading
         Get
         Get
             Return _TextError
             Return _TextError
         End Get
         End Get
-        Set(ByVal value As String)
+        Set(value As String)
             _TextError = value
             _TextError = value
             RefreshText()
             RefreshText()
         End Set
         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">
              HorizontalAlignment="Center" VerticalAlignment="Center" Background="{StaticResource ColorBrushSemiTransparent}" CornerRadius="13.5" MinHeight="27" MaxHeight="27">
     <StackPanel Orientation="Horizontal">
     <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" />
         <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>
     </StackPanel>
 </Border>
 </Border>

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

@@ -1,7 +1,7 @@
 Class MySearchBox
 Class MySearchBox
     Inherits MyCard
     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
     Private Sub MySearchBox_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
         TextBox.Focus()
         TextBox.Focus()
     End Sub
     End Sub
@@ -11,7 +11,7 @@
         Get
         Get
             Return TextBox.HintText
             Return TextBox.HintText
         End Get
         End Get
-        Set(ByVal value As String)
+        Set(value As String)
             TextBox.HintText = value
             TextBox.HintText = value
         End Set
         End Set
     End Property
     End Property
@@ -19,7 +19,7 @@
         Get
         Get
             Return TextBox.Text
             Return TextBox.Text
         End Get
         End Get
-        Set(ByVal value As String)
+        Set(value As String)
             TextBox.Text = value
             TextBox.Text = value
         End Set
         End Set
     End Property
     End Property

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

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

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

@@ -4,7 +4,7 @@
         xmlns:local="clr-namespace:PCL"
         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"
         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}"
         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>
     <Window.TaskbarItemInfo>
         <TaskbarItemInfo ThumbnailClipMargin="{Binding Margin, ElementName=PanForm}" />
         <TaskbarItemInfo ThumbnailClipMargin="{Binding Margin, ElementName=PanForm}" />
     </Window.TaskbarItemInfo>
     </Window.TaskbarItemInfo>

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

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

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

@@ -11,12 +11,12 @@ Public Module ModBase
 #Region "声明"
 #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
 #If BETA Then
-    Public Const VersionCode As Integer = 293 'Release
+    Public Const VersionCode As Integer = 296 'Release
 #Else
 #Else
-    Public Const VersionCode As Integer = 297 'Snapshot
+    Public Const VersionCode As Integer = 298 'Snapshot
 #End If
 #End If
     '自动生成的版本信息
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
     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)
             Pic = My.Application.TryFindResource(FilePathOrResourceName)
             If Pic Is Nothing Then
             If Pic Is Nothing Then
                 Pic = New System.Drawing.Bitmap(1, 1)
                 Pic = New System.Drawing.Bitmap(1, 1)
-                Log(ex, "加载位图失败(" & FilePathOrResourceName & ")")
+                Log(ex, $"加载位图失败({FilePathOrResourceName})")
                 Throw
                 Throw
             Else
             Else
-                Log(ex, "指定类型有误的位图加载(" & FilePathOrResourceName & ")", LogLevel.Developer)
+                Log(ex, $"指定类型有误的位图加载({FilePathOrResourceName})", LogLevel.Developer)
                 Exit Try
                 Exit Try
             End If
             End If
         End Try
         End Try

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

@@ -375,6 +375,7 @@
                     End Select
                     End Select
                 Next
                 Next
                 If Tags.Count = 0 Then Tags.Add("杂项")
                 If Tags.Count = 0 Then Tags.Add("杂项")
+                Tags.Sort()
                 ModLoaders.Sort()
                 ModLoaders.Sort()
 #End Region
 #End Region
             End If
             End If
@@ -424,15 +425,32 @@
             Dim ExNameList As List(Of String)
             Dim ExNameList As List(Of String)
             If TranslatedName = RawName Then
             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
             Else
                 '有中文翻译
                 '有中文翻译
                 '尝试将文本分为三段:FinalName (EnglishName) - Suffix
                 '尝试将文本分为三段:FinalName (EnglishName) - Suffix
@@ -452,21 +470,20 @@
                     ExNameList = New List(Of String) From {EnglishName} '使用原名
                     ExNameList = New List(Of String) From {EnglishName} '使用原名
                 End If
                 End If
                 '添加后缀
                 '添加后缀
-                If Suffix <> "" Then
-                    ExNameList.Add(Suffix)
-                    ExNameList = ExNameList.Distinct().ToList()
-                End If
+                If Suffix <> "" Then ExNameList.Add(Suffix)
             End If
             End If
+            ExNameList = ExNameList.Distinct().ToList()
             '显示 FinalName 与 ExNameList
             '显示 FinalName 与 ExNameList
-            NewItem.LabTitle.Text = FinalName
             If ExNameList.Count = 0 Then
             If ExNameList.Count = 0 Then
 NoExName:
 NoExName:
+                NewItem.LabTitle.Text = RawName
                 CType(NewItem.LabTitleRaw.Parent, StackPanel).Children.Remove(NewItem.LabTitleRaw)
                 CType(NewItem.LabTitleRaw.Parent, StackPanel).Children.Remove(NewItem.LabTitleRaw)
             Else
             Else
+                NewItem.LabTitle.Text = FinalName
                 Dim ExName As String = ""
                 Dim ExName As String = ""
                 For Each Ex In ExNameList
                 For Each Ex In ExNameList
                     '去除 “Forge/Fabric” 这一无意义提示
                     '去除 “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 版”
                     '将 “Forge” 等提示改为 “Forge 版”
                     If (Ex.ToLower.Contains("forge") OrElse Ex.ToLower.Contains("fabric") OrElse Ex.ToLower.Contains("quilt")) AndAlso Not Ex.Contains("版") AndAlso
                     If (Ex.ToLower.Contains("forge") OrElse Ex.ToLower.Contains("fabric") OrElse Ex.ToLower.Contains("quilt")) AndAlso Not Ex.Contains("版") AndAlso
                         Ex.ToLower.Replace("forge", "").Replace("fabric", "").Replace("quilt", "").Length <= 3 Then
                         Ex.ToLower.Replace("forge", "").Replace("fabric", "").Replace("quilt", "").Length <= 3 Then

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

@@ -365,7 +365,7 @@
         ShadersMod与Optifine同时安装
         ShadersMod与Optifine同时安装
         Forge安装不完整
         Forge安装不完整
         Mod需要Java11
         Mod需要Java11
-        Mod依赖项未完善
+        Mod缺少前置或MC版本错误
     End Enum
     End Enum
     ''' <summary>
     ''' <summary>
     ''' 根据 AnalyzeLogs 与可能的版本信息分析崩溃原因。
     ''' 根据 AnalyzeLogs 与可能的版本信息分析崩溃原因。
@@ -374,9 +374,11 @@
         Log("[Crash] 步骤 3:分析崩溃原因")
         Log("[Crash] 步骤 3:分析崩溃原因")
         LogAll = (If(LogMc, "") & If(LogHs, "") & If(LogCrash, "")).ToLower
         LogAll = (If(LogMc, "") & If(LogHs, "") & If(LogCrash, "")).ToLower
 
 
-        '1. 精准日志匹配 1
+        '1. 精准日志匹配,中/高优先级
         AnalyzeCrit1()
         AnalyzeCrit1()
         If CrashReasons.Count > 0 Then GoTo Done
         If CrashReasons.Count > 0 Then GoTo Done
+        AnalyzeCrit2()
+        If CrashReasons.Count > 0 Then GoTo Done
 
 
         '2. 堆栈分析
         '2. 堆栈分析
         If LogAll.Contains("forge") OrElse LogAll.Contains("fabric") OrElse LogAll.Contains("liteloader") Then
         If LogAll.Contains("forge") OrElse LogAll.Contains("fabric") OrElse LogAll.Contains("liteloader") Then
@@ -414,8 +416,8 @@
             Log("[Crash] 可能并未安装 Mod,不进行堆栈分析")
             Log("[Crash] 可能并未安装 Mod,不进行堆栈分析")
         End If
         End If
 
 
-        '3. 精准日志匹配 2
-        AnalyzeCrit2()
+        '3. 精准日志匹配,低优先级
+        AnalyzeCrit3()
 
 
         '输出到日志
         '输出到日志
 Done:
 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: java.base/jdk") Then AppendReason(CrashReason.使用JDK)
             If LogMc.Contains("java.lang.ClassCastException: class 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("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: '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("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("java.lang.NoSuchFieldException: ucp") Then AppendReason(CrashReason.Java版本过高)
             If LogMc.Contains("because module java.base does not export") 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("Invalid module name: '' is not a Java identifier") Then AppendReason(CrashReason.Mod名称包含特殊字符)
             If LogMc.Contains("has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to") Then AppendReason(CrashReason.Mod需要Java11)
             If LogMc.Contains("has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to") Then AppendReason(CrashReason.Mod需要Java11)
             If LogMc.Contains("java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual") Then AppendReason(CrashReason.Mod需要Java11)
             If LogMc.Contains("java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual") Then AppendReason(CrashReason.Mod需要Java11)
-            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("Invalid maximum heap size") Then AppendReason(CrashReason.使用32位Java导致JVM无法分配足够多的内存)
             If LogMc.Contains("Could not reserve enough space") Then
             If LogMc.Contains("Could not reserve enough space") Then
                 If LogMc.Contains("for 1048576KB object heap") Then
                 If LogMc.Contains("for 1048576KB object heap") Then
@@ -507,34 +508,17 @@ Done:
                     AppendReason(CrashReason.内存不足)
                     AppendReason(CrashReason.内存不足)
                 End If
                 End If
             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 重复安装
             '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("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("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))
             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
         End If
         End If
 
 
@@ -572,14 +556,43 @@ Done:
 
 
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
-    ''' 进行精准日志匹配。匹配优先级低于堆栈分析的崩溃。
+    ''' 进行精准日志匹配。匹配优先级高于堆栈分析的崩溃,但低于上面的。
+    ''' 如果第一步已经找到了原因则不执行该检测。
     ''' </summary>
     ''' </summary>
     Private Sub AnalyzeCrit2()
     Private Sub AnalyzeCrit2()
 
 
         '游戏日志分析
         '游戏日志分析
         If LogMc IsNot Nothing Then
         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)
                 AppendReason(CrashReason.极短的程序输出, LogMc)
             End If
             End If
             'Mixin 失败可以导致大量 Mod 实例创建失败
             'Mixin 失败可以导致大量 Mod 实例创建失败
@@ -814,7 +827,7 @@ NextStack:
                 Case CrashReason.Mod文件被解压
                 Case CrashReason.Mod文件被解压
                     Results.Add("由于 Mod 文件被解压了,导致游戏无法继续运行。\n直接把整个 Mod 文件放进 Mod 文件夹中即可,若解压就会导致游戏出错。\n\n请删除 Mod 文件夹中已被解压的 Mod,然后再启动游戏。")
                     Results.Add("由于 Mod 文件被解压了,导致游戏无法继续运行。\n直接把整个 Mod 文件放进 Mod 文件夹中即可,若解压就会导致游戏出错。\n\n请删除 Mod 文件夹中已被解压的 Mod,然后再启动游戏。")
                 Case CrashReason.内存不足
                 Case CrashReason.内存不足
-                    Results.Add("Minecraft 内存不足,导致其无法继续运行。\n这很可能是由于你为游戏分配的内存不足,或是游戏的配置要求过高。\n\n你可以在启动设置中增加为游戏分配的内存,删除配置要求较高的材质、Mod、光影。\n如果这依然不奏效,请在开始游戏前尽量关闭其他软件,或者……换台电脑?\h")
+                    Results.Add("Minecraft 内存不足,导致其无法继续运行。\n这很可能是因为电脑内存不足、游戏分配的内存不足,或是配置要求过高。\n\n你可以尝试在 更多 → 百宝箱 中选择 内存优化,然后再启动游戏。\n如果还是不行,请在启动设置中增加为游戏分配的内存,并删除配置要求较高的材质、Mod、光影。\n如果依然不奏效,请在开始游戏前尽量关闭其他软件,或者……换台电脑?\h")
                 Case CrashReason.使用OpenJ9
                 Case CrashReason.使用OpenJ9
                     Results.Add("游戏因为使用 Open J9 而崩溃了。\n请在启动设置的 Java 选择一项中改用非 OpenJ9 的 Java,然后再启动游戏。")
                     Results.Add("游戏因为使用 Open J9 而崩溃了。\n请在启动设置的 Java 选择一项中改用非 OpenJ9 的 Java,然后再启动游戏。")
                 Case CrashReason.使用JDK
                 Case CrashReason.使用JDK
@@ -837,6 +850,12 @@ NextStack:
                     Else
                     Else
                         Results.Add("你的游戏遇到了一些问题,这可能是某些 Mod 所引起的,PCL 找到了以下可疑的关键词:\n - " & Join(Additional, ", ") & "\n\n如果你知道这些关键词对应的 Mod,那么有可能就是它引起的错误,你也可以查看错误报告获取详情。\h")
                         Results.Add("你的游戏遇到了一些问题,这可能是某些 Mod 所引起的,PCL 找到了以下可疑的关键词:\n - " & Join(Additional, ", ") & "\n\n如果你知道这些关键词对应的 Mod,那么有可能就是它引起的错误,你也可以查看错误报告获取详情。\h")
                     End If
                     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名称
                 Case CrashReason.崩溃日志堆栈分析发现Mod名称
                     If Additional.Count = 1 Then
                     If Additional.Count = 1 Then
                         Results.Add("名为 " & Additional.First & " 的 Mod 可能导致了游戏出错。\n\e\h")
                         Results.Add("名为 " & Additional.First & " 的 Mod 可能导致了游戏出错。\n\e\h")
@@ -943,12 +962,6 @@ NextStack:
                     Else
                     Else
                         Results.Add("Forge 可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h")
                         Results.Add("Forge 可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h")
                     End If
                     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.没有可用的分析文件
                 Case CrashReason.没有可用的分析文件
                     Results.Add("你的游戏出现了一些问题,但 PCL 未能找到相关记录文件,因此无法进行分析。\h")
                     Results.Add("你的游戏出现了一些问题,但 PCL 未能找到相关记录文件,因此无法进行分析。\h")
                 Case Else
                 Case Else

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

@@ -1,5 +1,5 @@
 Public Module ModJava
 Public Module ModJava
-    Public JavaListCacheVersion As Integer = 4
+    Public JavaListCacheVersion As Integer = 5
 
 
     ''' <summary>
     ''' <summary>
     ''' 目前所有可用的 Java。
     ''' 目前所有可用的 Java。
@@ -129,8 +129,10 @@
                 End If
                 End If
                 Is64Bit = Output.Contains("64-bit")
                 Is64Bit = Output.Contains("64-bit")
                 If Version.Minor <= 4 OrElse Version.Minor >= 25 Then Throw New Exception("分析详细信息失败,获取的版本为 " & Version.ToString)
                 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
             Catch ex As Exception
                 Log("[Java] 检查失败的 Java 输出:" & PathFolder & "java.exe" & vbCrLf & If(Output, "无程序输出"))
                 Log("[Java] 检查失败的 Java 输出:" & PathFolder & "java.exe" & vbCrLf & If(Output, "无程序输出"))
                 Throw New Exception("检查 Java 失败(" & If(PathJavaw, "Nothing") & ")", ex)
                 Throw New Exception("检查 Java 失败(" & If(PathJavaw, "Nothing") & ")", ex)
@@ -230,6 +232,8 @@
                 Try
                 Try
                     UserJava = JavaEntry.FromJson(GetJson(Setup.Get("VersionArgumentJavaSelect", Version:=RelatedVersion)))
                     UserJava = JavaEntry.FromJson(GetJson(Setup.Get("VersionArgumentJavaSelect", Version:=RelatedVersion)))
                     UserJava.Check()
                     UserJava.Check()
+                Catch ex As ThreadInterruptedException
+                    Throw
                 Catch ex As Exception
                 Catch ex As Exception
                     UserJava = Nothing
                     UserJava = Nothing
                     Setup.Reset("VersionArgumentJavaSelect", Version:=RelatedVersion)
                     Setup.Reset("VersionArgumentJavaSelect", Version:=RelatedVersion)
@@ -242,6 +246,8 @@
                 Try
                 Try
                     UserJava = JavaEntry.FromJson(GetJson(Setup.Get("LaunchArgumentJavaSelect")))
                     UserJava = JavaEntry.FromJson(GetJson(Setup.Get("LaunchArgumentJavaSelect")))
                     UserJava.Check()
                     UserJava.Check()
+                Catch ex As ThreadInterruptedException
+                    Throw
                 Catch ex As Exception
                 Catch ex As Exception
                     UserJava = Nothing
                     UserJava = Nothing
                     Setup.Reset("LaunchArgumentJavaSelect")
                     Setup.Reset("LaunchArgumentJavaSelect")
@@ -450,7 +456,7 @@ NoUserJava:
         '4. Java 大版本
         '4. Java 大版本
         If Left.VersionCode <> Right.VersionCode Then
         If Left.VersionCode <> Right.VersionCode Then
             '                             Java  7   8   9  10  11  12 13 14 15  16  17  18  19  20...
             '                             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)
             Return Weight.ElementAtOrDefault(Left.VersionCode) >= Weight.ElementAtOrDefault(Right.VersionCode)
         End If
         End If
         '5. 最次级版本号更接近 51
         '5. 最次级版本号更接近 51
@@ -530,10 +536,10 @@ NoUserJava:
             For Each Pair In JavaPreList
             For Each Pair In JavaPreList
                 If Pair.Key.Contains("javapath_target_") OrElse Pair.Key.Contains("javatmp") Then
                 If Pair.Key.Contains("javapath_target_") OrElse Pair.Key.Contains("javatmp") Then
                     Log("[Java] 位于 " & Pair.Key & " 的 Java 包含特殊引用")
                     Log("[Java] 位于 " & Pair.Key & " 的 Java 包含特殊引用")
-                    Continue For
+                Else
+                    Log("[Java] 位于 " & Pair.Key & " 的 Java 不含特殊引用")
+                    JavaWithoutInherit.Add(Pair.Key, Pair.Value)
                 End If
                 End If
-                Log("[Java] 位于 " & Pair.Key & " 的 Java 不含特殊引用")
-                JavaWithoutInherit.Add(Pair.Key, Pair.Value)
             Next
             Next
             If JavaWithoutInherit.Count > 0 Then JavaPreList = JavaWithoutInherit
             If JavaWithoutInherit.Count > 0 Then JavaPreList = JavaWithoutInherit
 
 
@@ -688,18 +694,17 @@ Wait:
 
 
     ''' <summary>
     ''' <summary>
     ''' 提示 Java 缺失,并弹窗确认是否自动下载。返回玩家选择是否下载。
     ''' 提示 Java 缺失,并弹窗确认是否自动下载。返回玩家选择是否下载。
-    ''' 支持下载 Java 8、14、17,输入其他 VersionCode 会要求玩家自行下载。
     ''' </summary>
     ''' </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")
                      "未找到 Java")
             Return False
             Return False
+        Else
+            Return MyMsgBox($"PCL 未找到 {VersionDescription},是否需要 PCL 自动下载?" & vbCrLf &
+                            $"如果你已经安装了 {VersionDescription},请在 设置 → 启动选项 → 游戏 Java 中手动导入。",
+                            "未找到 Java", "自动下载", "取消") = 1
         End If
         End If
     End Function
     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
             If McVersionCurrent.Version.McName = "1.7.2" Then
                 '1.7.2:必须 Java 7
                 '1.7.2:必须 Java 7
                 MinVer = New Version(1, 7, 0, 0) : MaxVer = New Version(1, 7, 999, 999)
                 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)
                 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)
                 MinVer = New Version(1, 8, 0, 0) : MaxVer = New Version(1, 10, 999, 999)
             ElseIf McVersionCurrent.Version.McCodeMain = 15 Then
             ElseIf McVersionCurrent.Version.McCodeMain = 15 Then
                 '1.15:Java 8 - 15
                 '1.15:Java 8 - 15
@@ -1182,17 +1182,27 @@ SystemBrowser:
         If Task.IsAborted Then Exit Sub '中断加载会导致 JavaSelect 异常地返回空值,误判找不到 Java
         If Task.IsAborted Then Exit Sub '中断加载会导致 JavaSelect 异常地返回空值,误判找不到 Java
         McLaunchLog("无合适的 Java,需要确认是否自动下载")
         McLaunchLog("无合适的 Java,需要确认是否自动下载")
         Dim JavaCode As String
         Dim JavaCode As String
-        If MinVer >= New Version(1, 16, 0, 0) Then
+        If MinVer >= New Version(1, 9, 0, 0) Then
             JavaCode = 17
             JavaCode = 17
+            If Not JavaDownloadConfirm("Java 17") Then Throw New Exception("$$")
         ElseIf MaxVer < New Version(1, 8, 0, 0) Then
         ElseIf MaxVer < New Version(1, 8, 0, 0) Then
             JavaCode = 7
             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"
             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
         Else
             JavaCode = 8
             JavaCode = 8
+            If Not JavaDownloadConfirm("Java 8") Then Throw New Exception("$$")
         End If
         End If
-        If Not JavaDownloadConfirm(JavaCode) Then Throw New Exception("$$")
 
 
         '开始自动下载
         '开始自动下载
         Dim JavaLoader = JavaFixLoaders(JavaCode)
         Dim JavaLoader = JavaFixLoaders(JavaCode)
@@ -1350,7 +1360,7 @@ SystemBrowser:
 
 
         '添加 Java Wrapper 作为主 Jar
         '添加 Java Wrapper 作为主 Jar
         If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
         If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
-        DataList.Insert(0, "-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """")
+        DataList.Add("-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """")
         DataList.Add("-jar """ & ExtractJavaWrapper() & """")
         DataList.Add("-jar """ & ExtractJavaWrapper() & """")
 
 
         '添加 MainClass
         '添加 MainClass
@@ -1417,7 +1427,7 @@ NextVersion:
 
 
         '添加 Java Wrapper 作为主 Jar
         '添加 Java Wrapper 作为主 Jar
         If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
         If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
-        DataList.Insert(0, "-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """")
+        DataList.Add("-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """")
         DataList.Add("-jar """ & ExtractJavaWrapper() & """")
         DataList.Add("-jar """ & ExtractJavaWrapper() & """")
 
 
         '将 "-XXX" 与后面 "XXX" 合并到一起
         '将 "-XXX" 与后面 "XXX" 合并到一起

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

@@ -1,4 +1,5 @@
 Imports System.IO.Compression
 Imports System.IO.Compression
+Imports NAudio.Gui
 
 
 Public Module ModMinecraft
 Public Module ModMinecraft
 
 
@@ -392,7 +393,7 @@ Public Module ModMinecraft
                         '非准确的版本判断警告
                         '非准确的版本判断警告
                         Log("[Minecraft] 无法完全确认 MC 版本号的版本:" & Name)
                         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
                         If Regex IsNot Nothing Then
                             _Version.McName = Regex
                             _Version.McName = Regex
                             GoTo VersionSearchFinish
                             GoTo VersionSearchFinish
@@ -401,7 +402,7 @@ Public Module ModMinecraft
                         Dim JsonRaw As JObject = JsonObject.DeepClone()
                         Dim JsonRaw As JObject = JsonObject.DeepClone()
                         JsonRaw.Remove("libraries")
                         JsonRaw.Remove("libraries")
                         Dim JsonRawText As String = JsonRaw.ToString
                         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
                         If Regex IsNot Nothing Then
                             _Version.McName = Regex
                             _Version.McName = Regex
                             GoTo VersionSearchFinish
                             GoTo VersionSearchFinish
@@ -467,7 +468,7 @@ VersionSearchFinish:
                 End If
                 End If
                 Return _JsonText
                 Return _JsonText
             End Get
             End Get
-            Set(ByVal value As String)
+            Set(value As String)
                 _JsonText = value
                 _JsonText = value
             End Set
             End Set
         End Property
         End Property
@@ -562,7 +563,7 @@ Recheck:
                 End If
                 End If
                 Return _JsonObject
                 Return _JsonObject
             End Get
             End Get
-            Set(ByVal value As JObject)
+            Set(value As JObject)
                 _JsonObject = value
                 _JsonObject = value
             End Set
             End Set
         End Property
         End Property
@@ -966,7 +967,7 @@ ExitDataLoad:
                 End If
                 End If
                 Return _SortCode
                 Return _SortCode
             End Get
             End Get
-            Set(ByVal value As Integer)
+            Set(value As Integer)
                 _SortCode = value
                 _SortCode = value
             End Set
             End Set
         End Property
         End Property
@@ -1608,7 +1609,7 @@ NextVersion:
             Get
             Get
                 Return _Url
                 Return _Url
             End Get
             End Get
-            Set(ByVal value As String)
+            Set(value As String)
                 '孤儿 Forge 作者喜欢把没有的 URL 写个空字符串
                 '孤儿 Forge 作者喜欢把没有的 URL 写个空字符串
                 _Url = If(String.IsNullOrWhiteSpace(value), Nothing, value)
                 _Url = If(String.IsNullOrWhiteSpace(value), Nothing, value)
             End Set
             End Set
@@ -2122,30 +2123,46 @@ NextVersion:
 
 
             '确认 Virtual 与 Map 状态
             '确认 Virtual 与 Map 状态
             Dim IsVirtual As Boolean = False
             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
                 For Each File As JProperty In Json("objects").Children
                     McAssetsListGet.Add(New McAssetsToken With {
                     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
                 Next
             Else
             Else
                 For Each File As JProperty In Json("objects").Children
                 For Each File As JProperty In Json("objects").Children
                     McAssetsListGet.Add(New McAssetsToken With {
                     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
                 Next
             End If
             End If
 
 
@@ -2186,899 +2203,6 @@ NextVersion:
         Return Result
         Return Result
     End Function
     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
 #End Region
 
 
     ''' <summary>
     ''' <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
                 For Each ModJson In Task.Input
                     '跳过重复的 Mod(疑似 CurseForge Bug)
                     '跳过重复的 Mod(疑似 CurseForge Bug)
                     If FileList.ContainsKey(ModJson("id").ToObject(Of Integer)) Then Continue For
                     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
                     Dim IsResourcePack As Boolean
                     If ModJson("modules").Any Then 'modules 可能返回 null(#1006)
                     If ModJson("modules").Any Then 'modules 可能返回 null(#1006)
                         Dim ModuleNames = CType(ModJson("modules"), JArray).Select(Function(l) l("name").ToString).ToList
                         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
                     Else
                         IsResourcePack = False
                         IsResourcePack = False
                     End If
                     End If
                     '实际的添加
                     '实际的添加
-                    Dim File As New CompFile(ModJson, CompType.Mod)
-                    If Not File.Available Then Continue For
                     FileList.Add(ModJson("id"),
                     FileList.Add(ModJson("id"),
                                  File.ToNetFile($"{PathMcFolder}versions\{VersionName}\{If(IsResourcePack, "resourcepacks", "mods")}\"))
                                  File.ToNetFile($"{PathMcFolder}versions\{VersionName}\{If(IsResourcePack, "resourcepacks", "mods")}\"))
                     Task.Progress += 1 / (1 + ModFileList.Count)
                     Task.Progress += 1 / (1 + ModFileList.Count)
@@ -278,6 +282,7 @@ Retry:
         '构造加载器
         '构造加载器
         Dim Request As New McInstallRequest With {
         Dim Request As New McInstallRequest With {
             .TargetVersionName = VersionName,
             .TargetVersionName = VersionName,
+            .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
             .MinecraftName = Json("minecraft")("version").ToString,
             .MinecraftName = Json("minecraft")("version").ToString,
             .ForgeVersion = ForgeVersion,
             .ForgeVersion = ForgeVersion,
             .FabricVersion = FabricVersion
             .FabricVersion = FabricVersion
@@ -333,7 +338,7 @@ Retry:
 
 
         '启动
         '启动
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
-        Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
+        Loader.Start(Request.TargetVersionFolder)
         LoaderTaskbarAdd(Loader)
         LoaderTaskbarAdd(Loader)
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.ShowRefresh()
         If ShowRibble Then FrmMain.BtnExtraDownload.Ribble()
         If ShowRibble Then FrmMain.BtnExtraDownload.Ribble()
@@ -431,6 +436,7 @@ Retry:
         '构造加载器
         '构造加载器
         Dim Request As New McInstallRequest With {
         Dim Request As New McInstallRequest With {
             .TargetVersionName = VersionName,
             .TargetVersionName = VersionName,
+            .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
             .MinecraftName = MinecraftVersion,
             .MinecraftName = MinecraftVersion,
             .ForgeVersion = ForgeVersion,
             .ForgeVersion = ForgeVersion,
             .FabricVersion = FabricVersion
             .FabricVersion = FabricVersion
@@ -486,7 +492,7 @@ Retry:
 
 
         '启动
         '启动
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
-        Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
+        Loader.Start(Request.TargetVersionFolder)
         LoaderTaskbarAdd(Loader)
         LoaderTaskbarAdd(Loader)
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.ShowRefresh()
         If ShowRibble Then FrmMain.BtnExtraDownload.Ribble()
         If ShowRibble Then FrmMain.BtnExtraDownload.Ribble()
@@ -533,6 +539,7 @@ Retry:
         End If
         End If
         Dim Request As New McInstallRequest With {
         Dim Request As New McInstallRequest With {
             .TargetVersionName = VersionName,
             .TargetVersionName = VersionName,
+            .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
             .MinecraftName = Json("gameVersion").ToString
             .MinecraftName = Json("gameVersion").ToString
         }
         }
         Dim InstallExpectTime As Double = 0
         Dim InstallExpectTime As Double = 0
@@ -584,7 +591,7 @@ Retry:
         '启动
         '启动
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
         'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件”
         'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件”
-        Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
+        Loader.Start(Request.TargetVersionFolder)
         LoaderTaskbarAdd(Loader)
         LoaderTaskbarAdd(Loader)
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.Ribble()
         FrmMain.BtnExtraDownload.Ribble()
@@ -629,7 +636,7 @@ Retry:
             Hint("该整合包未提供游戏版本信息,无法安装!", HintType.Critical)
             Hint("该整合包未提供游戏版本信息,无法安装!", HintType.Critical)
             Exit Sub
             Exit Sub
         End If
         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")
         For Each Component In PackJson("components")
             Select Case If(Component("uid"), "").ToString
             Select Case If(Component("uid"), "").ToString
                 Case "org.lwjgl"
                 Case "org.lwjgl"
@@ -679,7 +686,7 @@ Retry:
 
 
         '启动
         '启动
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
-        Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
+        Loader.Start(Request.TargetVersionFolder)
         LoaderTaskbarAdd(Loader)
         LoaderTaskbarAdd(Loader)
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.Ribble()
         FrmMain.BtnExtraDownload.Ribble()
@@ -738,6 +745,7 @@ Retry:
         End If
         End If
         Dim Request As New McInstallRequest With {
         Dim Request As New McInstallRequest With {
             .TargetVersionName = VersionName,
             .TargetVersionName = VersionName,
+            .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
             .MinecraftName = Addons("game"),
             .MinecraftName = Addons("game"),
             .OptiFineVersion = If(Addons.ContainsKey("optifine"), Addons("optifine"), Nothing),
             .OptiFineVersion = If(Addons.ContainsKey("optifine"), Addons("optifine"), Nothing),
             .ForgeVersion = If(Addons.ContainsKey("forge"), Addons("forge"), 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}
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
         'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件”
         'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件”
-        Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
+        Loader.Start(Request.TargetVersionFolder)
         LoaderTaskbarAdd(Loader)
         LoaderTaskbarAdd(Loader)
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.Ribble()
         FrmMain.BtnExtraDownload.Ribble()

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

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

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

@@ -13,11 +13,11 @@
 
 
                 Case "打开网页"
                 Case "打开网页"
                     Data(0) = Data(0).Replace("\", "/")
                     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
                         Exit Sub
                     End If
                     End If
-                    Hint("正在打开网页,请稍候……")
+                    Hint("正在开启中,请稍候……")
                     OpenWebsite(Data(0))
                     OpenWebsite(Data(0))
 
 
                 Case "打开文件", "打开帮助"
                 Case "打开文件", "打开帮助"
@@ -65,15 +65,21 @@
                     RunInThread(Sub() PageOtherTest.MemoryOptimize(True))
                     RunInThread(Sub() PageOtherTest.MemoryOptimize(True))
 
 
                 Case "清理垃圾"
                 Case "清理垃圾"
-                    PageOtherTest.RubbishClear()
+                    RunInThread(Sub() PageOtherTest.RubbishClear())
 
 
                 Case "弹出窗口"
                 Case "弹出窗口"
                     MyMsgBox(Data(1).Replace("\n", vbCrLf), Data(0).Replace("\n", vbCrLf))
                     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 "下载文件"
                 Case "下载文件"
                     Data(0) = Data(0).Replace("\", "/")
                     Data(0) = Data(0).Replace("\", "/")
                     If Not (Data(0).StartsWith("http://") OrElse Data(0).StartsWith("https://")) Then
                     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
                         Exit Sub
                     End If
                     End If
                     PageOtherTest.StartCustomDownload(Data(0), GetFileNameFromPath(Data(0)))
                     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, "*.*")
                 For Each File In My.Computer.FileSystem.GetFiles(Path & "PCL\Musics\", FileIO.SearchOption.SearchAllSubDirectories, "*.*")
                     '文件夹可能会被加入 .ini 文件夹配置文件、一些乱七八糟的 .jpg 文件啥的
                     '文件夹可能会被加入 .ini 文件夹配置文件、一些乱七八糟的 .jpg 文件啥的
                     Dim Extend As String = File.Split(".").Last.ToLower
                     Dim Extend As String = File.Split(".").Last.ToLower
-                    If Not (Extend = "ini" OrElse Extend = "jpg" OrElse Extend = "txt" OrElse Extend = "cfg" OrElse Extend = "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)
                         MusicAllList.Add(File)
                     End If
                     End If
                 Next
                 Next
@@ -277,7 +277,7 @@
             Log(ex, "播放音乐失败(" & MusicCurrent & ")", LogLevel.Developer)
             Log(ex, "播放音乐失败(" & MusicCurrent & ")", LogLevel.Developer)
             If MusicAllList.Count > 1 Then
             If MusicAllList.Count > 1 Then
                 Thread.Sleep(1000)
                 Thread.Sleep(1000)
-                MusicStartPlay(DequeueNextMusicAddress)
+                MusicStartPlay(DequeueNextMusicAddress, IsFirstLoad)
             End If
             End If
         Finally
         Finally
             If CurrentWave IsNot Nothing Then CurrentWave.Dispose()
             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>
     ''' </summary>
     Public Timeout As Integer = 600000
     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 r As WebRequest = MyBase.GetWebRequest(address)
         Dim request = TryCast(r, HttpWebRequest)
         Dim request = TryCast(r, HttpWebRequest)
 
 
@@ -37,19 +37,19 @@ Public Class CookieWebClient
         Return r
         Return r
     End Function
     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)
         Dim response As WebResponse = MyBase.GetWebResponse(request, result)
         ReadCookies(response)
         ReadCookies(response)
         Return response
         Return response
     End Function
     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)
         Dim response As WebResponse = MyBase.GetWebResponse(request)
         ReadCookies(response)
         ReadCookies(response)
         Return response
         Return response
     End Function
     End Function
 
 
-    Private Sub ReadCookies(ByVal r As WebResponse)
+    Private Sub ReadCookies(r As WebResponse)
         Dim response = TryCast(r, HttpWebResponse)
         Dim response = TryCast(r, HttpWebResponse)
         If response IsNot Nothing Then
         If response IsNot Nothing Then
             Dim cookies As CookieCollection = response.Cookies
             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("")>
 <Assembly: NeutralResourcesLanguage("")>

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

@@ -386,7 +386,7 @@ Public Module ModDownloadLib
         '选择 Java
         '选择 Java
         Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
         Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
         If Java Is Nothing Then
         If Java Is Nothing Then
-            If Not JavaDownloadConfirm(17) Then Throw New Exception("由于未找到 Java,已取消安装。")
+            If Not JavaDownloadConfirm("Java 8 或更高版本") Then Throw New Exception("由于未找到 Java,已取消安装。")
             '开始自动下载
             '开始自动下载
             Dim JavaLoader = JavaFixLoaders(17)
             Dim JavaLoader = JavaFixLoaders(17)
             JavaLoader.Start(17, IsForceRestart:=True)
             JavaLoader.Start(17, IsForceRestart:=True)
@@ -489,7 +489,7 @@ Public Module ModDownloadLib
     ''' <summary>
     ''' <summary>
     ''' 获取下载某个 OptiFine 版本的加载器列表。
     ''' 获取下载某个 OptiFine 版本的加载器列表。
     ''' </summary>
     ''' </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)
         McFolder = If(McFolder, PathMcFolder)
@@ -553,14 +553,14 @@ Public Module ModDownloadLib
                                                                              '拷贝原版文件
                                                                              '拷贝原版文件
                                                                              If Not IsCustomFolder Then Exit Sub
                                                                              If Not IsCustomFolder Then Exit Sub
                                                                              SyncLock VanillaSyncLock
                                                                              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)
                                                                                  Directory.CreateDirectory(McFolder & "versions\" & DownloadInfo.Inherit)
                                                                                  If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json") Then
                                                                                  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")
                                                                                           McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json")
                                                                                  End If
                                                                                  End If
                                                                                  If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar") Then
                                                                                  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")
                                                                                               McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar")
                                                                                  End If
                                                                                  End If
                                                                              End SyncLock
                                                                              End SyncLock
@@ -1114,7 +1114,7 @@ Public Module ModDownloadLib
         '选择 Java
         '选择 Java
         Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
         Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
         If Java Is Nothing Then
         If Java Is Nothing Then
-            If Not JavaDownloadConfirm(17) Then Throw New Exception("由于未找到 Java,已取消安装。")
+            If Not JavaDownloadConfirm("Java 8 或更高版本") Then Throw New Exception("由于未找到 Java,已取消安装。")
             '开始自动下载
             '开始自动下载
             Dim JavaLoader = JavaFixLoaders(17)
             Dim JavaLoader = JavaFixLoaders(17)
             JavaLoader.Start(17, IsForceRestart:=True)
             JavaLoader.Start(17, IsForceRestart:=True)
@@ -1259,7 +1259,7 @@ Public Module ModDownloadLib
     ''' <summary>
     ''' <summary>
     ''' 获取下载某个 Forge 版本的加载器列表。
     ''' 获取下载某个 Forge 版本的加载器列表。
     ''' </summary>
     ''' </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)
         McFolder = If(McFolder, PathMcFolder)
@@ -1386,13 +1386,13 @@ Public Module ModDownloadLib
                                                                              '拷贝原版文件
                                                                              '拷贝原版文件
                                                                              If Not IsCustomFolder Then Exit Sub
                                                                              If Not IsCustomFolder Then Exit Sub
                                                                              SyncLock VanillaSyncLock
                                                                              SyncLock VanillaSyncLock
-                                                                                 Dim ClientName As String = New DirectoryInfo(ClientDownloadLoader.Input).Name
+                                                                                 Dim ClientName As String = GetFolderNameFromPath(ClientFolder)
                                                                                  Directory.CreateDirectory(McFolder & "versions\" & Inherit)
                                                                                  Directory.CreateDirectory(McFolder & "versions\" & Inherit)
                                                                                  If Not File.Exists(McFolder & "versions\" & Inherit & "\" & Inherit & ".json") Then
                                                                                  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
                                                                                  End If
                                                                                  If Not File.Exists(McFolder & "versions\" & Inherit & "\" & Inherit & ".jar") Then
                                                                                  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 If
                                                                              End SyncLock
                                                                              End SyncLock
 #End Region
 #End Region
@@ -1797,6 +1797,10 @@ Public Module ModDownloadLib
         ''' 必填。安装目标版本名称。
         ''' 必填。安装目标版本名称。
         ''' </summary>
         ''' </summary>
         Public TargetVersionName As String
         Public TargetVersionName As String
+        ''' <summary>
+        ''' 必填。安装目标文件夹。
+        ''' </summary>
+        Public TargetVersionFolder As String
 
 
         ''' <summary>
         ''' <summary>
         ''' 必填。欲下载的 Minecraft 的版本名。
         ''' 必填。欲下载的 Minecraft 的版本名。
@@ -1909,7 +1913,7 @@ Public Module ModDownloadLib
             Dim Loader As New LoaderCombo(Of String)(Request.TargetVersionName & " 安装", SubLoaders) With {.OnStateChanged = AddressOf McInstallState}
             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)
             LoaderTaskbarAdd(Loader)
             FrmMain.BtnExtraDownload.ShowRefresh()
             FrmMain.BtnExtraDownload.ShowRefresh()
             FrmMain.BtnExtraDownload.Ribble()
             FrmMain.BtnExtraDownload.Ribble()
@@ -2009,28 +2013,29 @@ Public Module ModDownloadLib
             If OptiFineAsMod Then
             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})
                 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
             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
         End If
         End If
         'Forge
         'Forge
         If Request.ForgeVersion IsNot Nothing Then
         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
         End If
         'LiteLoader
         'LiteLoader
         If Request.LiteLoaderEntry IsNot Nothing Then
         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})
             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
         End If
-        'Fabric Loader
+        'Fabric
         If Request.FabricVersion IsNot Nothing Then
         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})
             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
         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
         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
         (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。
     ''' 获取 FabricApi 的加载异常信息。若正常则返回 Nothing。
     ''' </summary>
     ''' </summary>
     Private Function LoadFabricApiGetError() As String
     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.Run Then Return "正在获取版本列表……"
         If LoadFabricApi.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadFabricApi.State, Object).Error.Message
         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
         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
         Next
         Return "没有可用版本"
         Return "没有可用版本"
     End Function
     End Function
@@ -1032,14 +1037,21 @@
     ''' 获取 OptiFabric 的加载异常信息。若正常则返回 Nothing。
     ''' 获取 OptiFabric 的加载异常信息。若正常则返回 Nothing。
     ''' </summary>
     ''' </summary>
     Private Function LoadOptiFabricGetError() As String
     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 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.Run Then Return "正在获取版本列表……"
         If LoadOptiFabric.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadOptiFabric.State, Object).Error.Message
         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
         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
         Next
         Return "没有可用版本"
         Return "没有可用版本"
     End Function
     End Function
@@ -1114,6 +1126,7 @@
         '提交安装申请
         '提交安装申请
         Dim Request As New McInstallRequest With {
         Dim Request As New McInstallRequest With {
             .TargetVersionName = TextSelectName.Text,
             .TargetVersionName = TextSelectName.Text,
+            .TargetVersionFolder = $"{PathMcFolder}versions\{TextSelectName.Text}\",
             .MinecraftJson = SelectedMinecraftJsonUrl,
             .MinecraftJson = SelectedMinecraftJsonUrl,
             .MinecraftName = SelectedMinecraftId,
             .MinecraftName = SelectedMinecraftId,
             .OptiFineEntry = SelectedOptiFine,
             .OptiFineEntry = SelectedOptiFine,

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

@@ -5,7 +5,7 @@
 
 
     '皮肤储存
     '皮肤储存
     Private _Address As String
     Private _Address As String
-    Public Property Address() As String
+    Public Property Address As String
         Get
         Get
             Return _Address
             Return _Address
         End Get
         End Get
@@ -76,12 +76,19 @@
     ''' </summary>
     ''' </summary>
     Public Sub Load()
     Public Sub Load()
         Try
         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)
             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
             ImgBack.Tag = Address
             '大小检查
             '大小检查
             Dim Scale As Integer = Image.Pic.Width / 64
             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 IsLoad As Boolean = False
     Private Sub OnLoaded() Handles Me.Loaded
     Private Sub OnLoaded() Handles Me.Loaded
+        FormMain.EndProgramForce(Result.Aborted)
         If IsLoad Then Exit Sub
         If IsLoad Then Exit Sub
         IsLoad = True
         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 IsLoad As Boolean = False
     Private Sub MeLoaded() Handles Me.Loaded
     Private Sub MeLoaded() Handles Me.Loaded
+        FormMain.EndProgramForce(Result.Aborted)
 
 
         '重复加载部分
         '重复加载部分
         PanBack.ScrollToHome()
         PanBack.ScrollToHome()

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

@@ -187,6 +187,7 @@
                     End Try
                     End Try
                     FrmSpeedRight.PanMain.Children.Insert(0, Card)
                     FrmSpeedRight.PanMain.Children.Insert(0, Card)
                     RightCards.Add(Loader.Name, 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}
                     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)
                     Card.Children.Add(Cancel)
@@ -194,6 +195,8 @@
                                                  AniDispose(sender, False)
                                                  AniDispose(sender, False)
                                                  AniDispose(Card, True, Sub() If FrmSpeedRight.PanMain.Children.Count = 0 AndAlso FrmMain.PageCurrent = FormMain.PageType.DownloadManager Then FrmMain.PageBack())
                                                  AniDispose(Card, True, Sub() If FrmSpeedRight.PanMain.Children.Count = 0 AndAlso FrmMain.PageCurrent = FormMain.PageType.DownloadManager Then FrmMain.PageBack())
                                                  RightCards.Remove(Loader.Name)
                                                  RightCards.Remove(Loader.Name)
+                                                 LoaderTaskbar.Remove(Loader)
+                                                 Log($"[Watcher] 关闭下载管理卡片:{Loader.Name}")
                                                  RunInThread(Sub() Loader.Abort())
                                                  RunInThread(Sub() Loader.Abort())
                                              End Sub
                                              End Sub
                     '如果已经失败,再刷新一次,修改成失败的控件
                     '如果已经失败,再刷新一次,修改成失败的控件
@@ -217,6 +220,7 @@
                             Dim Card As Grid = RightCards(Loader.Name)
                             Dim Card As Grid = RightCards(Loader.Name)
                             FrmSpeedRight.PanMain.Children.Remove(Card)
                             FrmSpeedRight.PanMain.Children.Remove(Card)
                             RightCards.Remove(Loader.Name)
                             RightCards.Remove(Loader.Name)
+                            Log($"[Watcher] 移除下载管理卡片:{Loader.Name}")
                         End Sub)
                         End Sub)
         End If
         End If
     End Sub
     End Sub

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

@@ -7,22 +7,20 @@
     <Grid>
     <Grid>
         <Grid x:Name="PanAllBack">
         <Grid x:Name="PanAllBack">
             <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
             <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-                <StackPanel x:Name="PanMain" Margin="25,10" 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:MySearchBox Margin="0,15" HintText="搜索 Mod" x:Name="SearchBox" />
                     <local:MyCard Grid.Row="0" Margin="0,0,0,15" Title="管理" x:Name="PanManage">
                     <local:MyCard Grid.Row="0" Margin="0,0,0,15" Title="管理" x:Name="PanManage">
                         <StackPanel Margin="25,40,15,20">
                         <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 Height="35">
                                 <Grid.ColumnDefinitions>
                                 <Grid.ColumnDefinitions>
                                     <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                                     <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                                     <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                                     <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                                     <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                                     <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                                    <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                                 </Grid.ColumnDefinitions>
                                 </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="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>
                             </Grid>
                         </StackPanel>
                         </StackPanel>
                     </local:MyCard>
                     </local:MyCard>
@@ -47,5 +45,22 @@
         <local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40,0" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False">
         <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:MyLoading Text="正在加载 Mod 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
         </local:MyCard>
         </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>
     </Grid>
 </local:MyPageRight>
 </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 "初始化"
 #Region "初始化"
 
 
@@ -6,7 +9,11 @@
     Public Sub PageOther_Loaded() Handles Me.Loaded
     Public Sub PageOther_Loaded() Handles Me.Loaded
 
 
         PanBack.ScrollToHome()
         PanBack.ScrollToHome()
+        AniControlEnabled += 1
+        SelectedMods.Clear()
         RefreshList()
         RefreshList()
+        ChangeAllSelected(False)
+        AniControlEnabled -= 1
 
 
         '非重复加载部分
         '非重复加载部分
         If IsLoad Then Exit Sub
         If IsLoad Then Exit Sub
@@ -42,6 +49,7 @@
 
 
 #Region "UI 化"
 #Region "UI 化"
 
 
+    Private PanItems As StackPanel
     ''' <summary>
     ''' <summary>
     ''' 将 Mod 列表加载为 UI。
     ''' 将 Mod 列表加载为 UI。
     ''' </summary>
     ''' </summary>
@@ -62,13 +70,13 @@
 
 
             SearchBox.Text = ""
             SearchBox.Text = ""
             '建立 StackPanel
             '建立 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
             For Each ModEntity As McMod In List
-                NewStack.Children.Add(McModListItem(ModEntity))
+                PanItems.Children.Add(McModListItem(ModEntity))
             Next
             Next
             '建立 MyCard
             '建立 MyCard
             Dim NewCard As New MyCard With {.Title = McModGetTitle(List), .Margin = New Thickness(0, 0, 0, 15)}
             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)
             PanList.Children.Add(NewCard)
             '显示提示
             '显示提示
             If List.Count > 0 AndAlso Not Setup.Get("HintModDisable") Then
             If List.Count > 0 AndAlso Not Setup.Get("HintModDisable") Then
@@ -88,20 +96,12 @@
         For Each ModEntity As McMod In List
         For Each ModEntity As McMod In List
             Counter(ModEntity.State) += 1
             Counter(ModEntity.State) += 1
         Next
         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
     End Function
     Private Function McModListItem(Entry As McMod) As MyListItem
     Private Function McModListItem(Entry As McMod) As MyListItem
         '图标
         '图标
@@ -138,16 +138,21 @@
         '    Desc += " : " & Join(DepList, ", ")
         '    Desc += " : " & Join(DepList, ", ")
         'End If
         '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
         Return NewItem
     End Function
     End Function
     Private Sub McModContent(sender As MyListItem, e As EventArgs)
     Private Sub McModContent(sender As MyListItem, e As EventArgs)
         Dim ModEntity As McMod = sender.Tag
         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}
         Dim BtnDel As New MyIconButton With {.LogoScale = 1.1, .Logo = Logo.IconButtonDelete, .Tag = sender}
         BtnDel.ToolTip = "删除"
         BtnDel.ToolTip = "删除"
@@ -161,18 +166,14 @@
         ToolTipService.SetVerticalOffset(BtnOpen, 30)
         ToolTipService.SetVerticalOffset(BtnOpen, 30)
         ToolTipService.SetHorizontalOffset(BtnOpen, 2)
         ToolTipService.SetHorizontalOffset(BtnOpen, 2)
         AddHandler BtnOpen.Click, AddressOf Open_Click
         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 Sub
 
 
 #End Region
 #End Region
@@ -210,13 +211,144 @@
 #End If
 #End If
 
 
     ''' <summary>
     ''' <summary>
-    ''' 一键启用 / 禁用。
+    ''' 全选。
     ''' </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 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
             Dim NewPath As String = Nothing
             If ModEntity.State = McMod.McModState.Fine And IsDisable Then
             If ModEntity.State = McMod.McModState.Fine And IsDisable Then
                 '禁用
                 '禁用
@@ -225,7 +357,6 @@
                 '启用
                 '启用
                 NewPath = ModEntity.Path.Substring(0, ModEntity.Path.Count - ".disabled".Count)
                 NewPath = ModEntity.Path.Substring(0, ModEntity.Path.Count - ".disabled".Count)
             Else
             Else
-                SuccessfulCount += 1
                 Continue For
                 Continue For
             End If
             End If
             '重命名
             '重命名
@@ -233,27 +364,99 @@
                 If File.Exists(NewPath) AndAlso Not File.Exists(ModEntity.Path) Then Continue For '因为未知原因 Mod 的状态已经切换完了
                 If File.Exists(NewPath) AndAlso Not File.Exists(ModEntity.Path) Then Continue For '因为未知原因 Mod 的状态已经切换完了
                 File.Delete(NewPath)
                 File.Delete(NewPath)
                 FileSystem.Rename(ModEntity.Path, NewPath)
                 FileSystem.Rename(ModEntity.Path, NewPath)
-                SuccessfulCount += 1
             Catch ex As FileNotFoundException
             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
             Catch ex As Exception
-                Log(ex, "全局状态改变中重命名 Mod 失败")
+                Log(ex, $"批量重命名 Mod 失败({If(ModEntity.Path, "null")})")
                 IsSuccessful = False
                 IsSuccessful = False
             End Try
             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
         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 Sub
 
 
 #End Region
 #End Region
 
 
 #Region "单个 Mod 项"
 #Region "单个 Mod 项"
 
 
-    '点击:切换可用状态 / 显示错误原因
-    Public Sub Item_Click(sender As MyListItem, e As EventArgs)
+    '点击 Logo:切换可用状态 / 显示错误原因
+    Public Sub ItemLogo_Click(sender As MyListItem, e As EventArgs)
         Try
         Try
 
 
             Dim ModEntity As McMod = sender.Tag
             Dim ModEntity As McMod = sender.Tag
@@ -291,12 +494,13 @@
             Parent.Children.RemoveAt(IndexOfUi)
             Parent.Children.RemoveAt(IndexOfUi)
             Parent.Children.Insert(IndexOfUi, McModListItem(NewModEntity))
             Parent.Children.Insert(IndexOfUi, McModListItem(NewModEntity))
             '仅在非搜索页面才执行,以确保搜索页内外显示一致
             '仅在非搜索页面才执行,以确保搜索页内外显示一致
-            If String.IsNullOrWhiteSpace(SearchBox.Text) Then
+            If Not IsSearching Then
                 '改变禁用数量的显示
                 '改变禁用数量的显示
                 CType(Parent.Parent, MyCard).Title = McModGetTitle(McModLoader.Output)
                 CType(Parent.Parent, MyCard).Title = McModGetTitle(McModLoader.Output)
                 '更新加载器状态
                 '更新加载器状态
                 LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.UpdateOnly)
                 LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.UpdateOnly)
             End If
             End If
+            RefreshBottomBar()
 
 
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "单个状态改变中重命名 Mod 失败")
             Log(ex, "单个状态改变中重命名 Mod 失败")
@@ -358,27 +562,30 @@
             Dim ListItem As MyListItem
             Dim ListItem As MyListItem
             ListItem = If(TypeOf sender Is MyIconButton, sender.Tag, sender)
             ListItem = If(TypeOf sender Is MyIconButton, sender.Tag, sender)
             Dim ModEntity As McMod = ListItem.Tag
             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)
             Dim ContentLines As New List(Of String)
             If ModEntity.Description IsNot Nothing Then ContentLines.Add(ModEntity.Description & vbCrLf)
             If ModEntity.Description IsNot Nothing Then ContentLines.Add(ModEntity.Description & vbCrLf)
             If ModEntity.Authors IsNot Nothing Then ContentLines.Add("作者:" & ModEntity.Authors)
             If ModEntity.Authors IsNot Nothing Then ContentLines.Add("作者:" & ModEntity.Authors)
             ContentLines.Add("文件:" & ModEntity.FileName & "(" & GetString(New FileInfo(ModEntity.Path).Length) & ")")
             ContentLines.Add("文件:" & ModEntity.FileName & "(" & GetString(New FileInfo(ModEntity.Path).Length) & ")")
             If ModEntity.Version IsNot Nothing Then ContentLines.Add("版本:" & ModEntity.Version)
             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
             End If
             '获取用于搜索的 Mod 名称
             '获取用于搜索的 Mod 名称
             Dim ModOriginalName As String = ModEntity.Name.Replace(" ", "+")
             Dim ModOriginalName As String = ModEntity.Name.Replace(" ", "+")
@@ -426,22 +633,14 @@
 
 
 #End Region
 #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
     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))
             Dim QueryList As New List(Of SearchEntry(Of McMod))
             For Each Entry As McMod In McModLoader.Output
             For Each Entry As McMod In McModLoader.Output
@@ -472,15 +671,25 @@
             '显示
             '显示
             AniStart({
             AniStart({
                      AaOpacity(PanList, -PanList.Opacity, 100),
                      AaOpacity(PanList, -PanList.Opacity, 100),
-                     AaOpacity(PanManage, -PanManage.Opacity, 100),
                      AaCode(Sub()
                      AaCode(Sub()
                                 PanList.Visibility = Visibility.Collapsed
                                 PanList.Visibility = Visibility.Collapsed
-                                PanManage.Visibility = Visibility.Collapsed
                                 PanSearch.Visibility = Visibility.Visible
                                 PanSearch.Visibility = Visibility.Visible
                                 PanSearch.TriggerForceResize()
                                 PanSearch.TriggerForceResize()
                             End Sub,, True),
                             End Sub,, True),
                      AaOpacity(PanSearch, 1 - PanSearch.Opacity, 200, 60)
                      AaOpacity(PanSearch, 1 - PanSearch.Opacity, 200, 60)
                 }, "FrmVersionMod Search Switch", True)
                 }, "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 If
     End Sub
     End Sub
 
 

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

@@ -7,8 +7,8 @@
              d:DesignWidth="800" d:DesignHeight="800">
              d:DesignWidth="800" d:DesignHeight="800">
     <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
     <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
         <StackPanel x:Name="PanMain" Margin="25,10">
         <StackPanel x:Name="PanMain" Margin="25,10">
-          <local: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">
                 <StackPanel Margin="25,40,25,21">
                     <Grid>
                     <Grid>
                         <Grid.ColumnDefinitions>
                         <Grid.ColumnDefinitions>
@@ -215,4 +215,4 @@
             </local:MyCard>
             </local:MyCard>
         </StackPanel>
         </StackPanel>
     </local:MyScrollViewer>
     </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\MyCard.vb" />
     <Compile Include="Controls\MyPageRight.vb" />
     <Compile Include="Controls\MyPageRight.vb" />
     <Compile Include="Controls\MyPageLeft.vb" />
     <Compile Include="Controls\MyPageLeft.vb" />
+    <Compile Include="Controls\MyIconTextButton.xaml.vb">
+      <DependentUpon>MyIconTextButton.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Modules\Minecraft\ModComp.vb" />
     <Compile Include="Modules\Minecraft\ModComp.vb" />
     <Compile Include="Modules\Minecraft\ModJava.vb" />
     <Compile Include="Modules\Minecraft\ModJava.vb" />
+    <Compile Include="Modules\Minecraft\ModMod.vb" />
     <Compile Include="Modules\Minecraft\ModModpack.vb" />
     <Compile Include="Modules\Minecraft\ModModpack.vb" />
     <Compile Include="Modules\ModEvent.vb" />
     <Compile Include="Modules\ModEvent.vb" />
     <Compile Include="Controls\MyExtraButton.xaml.vb">
     <Compile Include="Controls\MyExtraButton.xaml.vb">
@@ -406,6 +410,10 @@
       <SubType>Designer</SubType>
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
       <Generator>MSBuild:Compile</Generator>
     </Page>
     </Page>
+    <Page Include="Controls\MyIconTextButton.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="Controls\MySearchBox.xaml">
     <Page Include="Controls\MySearchBox.xaml">
       <SubType>Designer</SubType>
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
       <Generator>MSBuild:Compile</Generator>

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

@@ -103,26 +103,34 @@
 </local:MyCard>
 </local:MyCard>
 
 
 <local:MyCard Title="事件 4:其他事件类型" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="事件 4:其他事件类型" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
-    <StackPanel Margin="25,40,23,15">
-        <TextBlock 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 共有以下种类:" />
                     Text="EventType 共有以下种类:" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+		<TextBlock Margin="0,0,0,4"
                     Text="· 打开网页、启动游戏、打开文件:已在上方的卡片中介绍" />
                     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 中的内容复制到剪贴板" />
                     Text="· 复制文本:将 EventData 中的内容复制到剪贴板" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+		<TextBlock Margin="0,0,0,4"
                     Text="· 刷新主页:点击重新加载主页。如果主页文件为联网更新,这会重新下载主页内容" />
                     Text="· 刷新主页:点击重新加载主页。如果主页文件为联网更新,这会重新下载主页内容" />
-        <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
+		<TextBlock Margin="0,0,0,4"
                     Text="· 下载文件:在点击时要求选择文件保存位置,并且下载 EventData 的链接中的文件" />
                     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" />
                     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 文件夹的路径)"/>
                     Text="· 打开帮助:跳转到 EventData 对应的帮助文件(输入相对 Help 文件夹的路径)"/>
-        <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
                     Text="打开自定义帮助页面" EventType="打开帮助" EventData="帮助/自定义帮助.json"/>
                     Text="打开自定义帮助页面" EventType="打开帮助" EventData="帮助/自定义帮助.json"/>
-    </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>
 
 
 <local:MyCard Title="横向布局 1:StackPanel" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
 <local:MyCard Title="横向布局 1:StackPanel" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">

BIN=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
 industrial-craft|工业时代2 (Industrial Craft 2)|515771
-
+@redpower2-core|红石力量2 (RedPower2)|71843
 buildcraft@|建筑 (BuildCraft)|884720
 buildcraft@|建筑 (BuildCraft)|884720
 forestry@|林业*|708812
 forestry@|林业*|708812
 railcraft|铁路*|603270
 railcraft|铁路*|603270
@@ -17,7 +17,7 @@ powerconverters-portablejims-fork|能源转换 (PowerConverters)
 
 
 modular-forcefield-system|模块化力场系统*
 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)
 balkons-weaponmod|更多武器 (Balkon's WeaponMod)
 advanced-machines|高级机械 (AdvancedMachines)
 advanced-machines|高级机械 (AdvancedMachines)
 advanced-solar-panels|高级太阳能*
 advanced-solar-panels|高级太阳能*
@@ -110,7 +110,7 @@ biomes-o-plenty|超多生物群系 (Biomes O' Plenty)|814732
 
 
 enhanced-portals-3|增强传送门3*
 enhanced-portals-3|增强传送门3*
 compact-solars|压缩太阳能阵列 (CompactSolars / Compact Solar Arrays)|807551
 compact-solars|压缩太阳能阵列 (CompactSolars / Compact Solar Arrays)|807551
-bibliocraft|收藏馆 (BiblioCraft)
+bibliocraft|收藏馆 (BiblioCraft)|644088
 natura|自然*
 natura|自然*
 atomic-science|原子科学*
 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
 dimensionaldoors|次元门 (Dimensional Doors)|896027
 chickenchunks|区块载入器 (ChickenChunks)
 chickenchunks|区块载入器 (ChickenChunks)
@@ -206,7 +206,7 @@ highlands|高地*
 doggy-talents|小狗天才*|713458
 doggy-talents|小狗天才*|713458
 plasmacraft|电浆工艺 (PlasmaCraft)
 plasmacraft|电浆工艺 (PlasmaCraft)
 
 
-fossils|考古与化石 (Fossils and Archeology Revival)|856874
+fossils@fossils-and-archeology-revival|考古与化石 (Fossils and Archeology Revival)|856874
 glenns-gases|格林的气体 (Glenn's Gases)
 glenns-gases|格林的气体 (Glenn's Gases)
 the-lord-of-the-rings-mod-legacy|魔戒:传承 (The Lord of the Rings Mod: Legacy)|363128
 the-lord-of-the-rings-mod-legacy|魔戒:传承 (The Lord of the Rings Mod: Legacy)|363128
 thaumic-tinkerer|神秘工匠*
 thaumic-tinkerer|神秘工匠*
@@ -214,14 +214,14 @@ thaumic-tinkerer|神秘工匠*
 
 
 
 
 xycraft
 xycraft
-chococraft|陆行鸟 (ChocoCraft)
-pams-harvestcraft|潘马斯农场 (Pam's HarvestCraft)|669856
+chococraft@|陆行鸟 (ChocoCraft)
+pams-harvestcraft@|潘马斯农场 (Pam's HarvestCraft)|669856
 
 
 opis
 opis
 
 
 openblocks|开放式方块 (OpenBlocks)
 openblocks|开放式方块 (OpenBlocks)
 
 
-
+water-power|水力科技*|190676
 
 
 
 
 randomcraft-v0-1-0|随机工艺 (RandomCraft)
 randomcraft-v0-1-0|随机工艺 (RandomCraft)
@@ -256,7 +256,7 @@ gravitation-suite|重力装甲*|734193
 redstone-arsenal|红石兵工厂*¨@redstone-arsenal|红石兵工厂 (Redstone Arsenal)
 redstone-arsenal|红石兵工厂*¨@redstone-arsenal|红石兵工厂 (Redstone Arsenal)
 big-reactors|大型反应堆*|348989
 big-reactors|大型反应堆*|348989
 
 
-endertanks|末影水槽 (Ender Tanks)
+endertanks|末影水槽 (Ender Tanks) - Forge¨endertanks-fabric|末影水槽 (Ender Tanks) - Fabric
 applied-energistics-2@ae2|应用能源2*|798231
 applied-energistics-2@ae2|应用能源2*|798231
 hopper-ducts|漏斗管道*
 hopper-ducts|漏斗管道*
 autofish|自动钓鱼*|630098
 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)
 per-fabrica-ad-astra-geologica|自合成至星辰:真实地理 (Per Fabrica Ad Astra: Geologica)
 
 
 redstone-furnace|红石炉*
 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!)
 tubes|管道 (Tubes!)
@@ -283,7 +283,7 @@ project-bench|高级工作台*
 
 
 apples-plus|更多苹果 (Apples+)
 apples-plus|更多苹果 (Apples+)
 
 
-qcraft|量子物理 (QCraft)
+
 
 
 mad-science|疯狂科学*
 mad-science|疯狂科学*
 
 
@@ -293,7 +293,7 @@ the-erebus|混沌之地*|856742
 emerald-mod|绿宝石Mod*|282677
 emerald-mod|绿宝石Mod*|282677
 lucky-block-official|幸运方块 (Lucky Block)
 lucky-block-official|幸运方块 (Lucky Block)
 dynamic-lights|动态光源*|593066
 dynamic-lights|动态光源*|593066
-yarr-cute-mob-models-remake|怪物娘化 (Yarr Cute Mob Models - Remake)|172434
+
 monster-hunter-frontier-craft|怪物猎人边境工艺*
 monster-hunter-frontier-craft|怪物猎人边境工艺*
 zombie-awareness|僵尸意识*
 zombie-awareness|僵尸意识*
 remote-io|远程接口 (Remote IO)
 remote-io|远程接口 (Remote IO)
@@ -358,7 +358,7 @@ falling-meteors-mod|陨石 (Falling Meteors)
 
 
 
 
 glacia-dimension|冰河时代 (Glacia)|1028541
 glacia-dimension|冰河时代 (Glacia)|1028541
-water-power|水力科技*|190676
+
 realtrainmod|真实火车 (RealTrainMod)|781058
 realtrainmod|真实火车 (RealTrainMod)|781058
 applemilktea2|苹果奶茶2 (AppleMilkTea2)|466114
 applemilktea2|苹果奶茶2 (AppleMilkTea2)|466114
 
 
@@ -374,7 +374,7 @@ notenoughkeys|更好的按键控制 (Not Enough Keys)
 
 
 
 
 
 
-
+crafting-pillar-mod|合成柱*
 ultratech|次元科技 (UltraTech)
 ultratech|次元科技 (UltraTech)
 
 
 atomicstrykers-infernal-mobs|稀有精英怪 (AtomicStryker's Infernal Mobs)|592395
 atomicstrykers-infernal-mobs|稀有精英怪 (AtomicStryker's Infernal Mobs)|592395
@@ -384,7 +384,7 @@ pchan3-steamship-steamboat-pirates|飞艇与汽船 (SteamShip - SteamBoat and Pi
 
 
 thaumic-energistics|神秘能源*|791638
 thaumic-energistics|神秘能源*|791638
 
 
-
+chisel|凿子*|641383
 nuclearcraft-mod|核电工艺 (NuclearCraft)
 nuclearcraft-mod|核电工艺 (NuclearCraft)
 opencomputers|开放式电脑 (Open Computers)|618383
 opencomputers|开放式电脑 (Open Computers)|618383
 elemental-stones|元素之石*
 elemental-stones|元素之石*
@@ -445,7 +445,7 @@ buildcraft-additions|建筑扩充*
 bedrockium-mod|基岩工艺*
 bedrockium-mod|基岩工艺*
 blocksteps
 blocksteps
 thaumic-horizons|神秘视界*
 thaumic-horizons|神秘视界*
-modtweaker
+modtweaker@
 lootbags|战利品 (Loot Bags)|720841
 lootbags|战利品 (Loot Bags)|720841
 compact-lava-generator|高级地热发电机*|522808
 compact-lava-generator|高级地热发电机*|522808
 project-zulu|祖鲁*
 project-zulu|祖鲁*
@@ -469,7 +469,7 @@ cooking-for-blockheads|懒人厨房 (Cooking for Blockheads) - Forge|545680¨coo
 
 
 psi@
 psi@
 good-nights-sleep|夜眠梦境/晚安的梦境 (Good Night's Sleep)|965322
 good-nights-sleep|夜眠梦境/晚安的梦境 (Good Night's Sleep)|965322
-binnies-mods|宾尼的模组合集 (Binnie's Mods)
+binnies-mods@|宾尼的模组合集 (Binnie's Mods)
 baubles|饰品栏*
 baubles|饰品栏*
 more-swords-mod|更多剑 (MoreSwords)
 more-swords-mod|更多剑 (MoreSwords)
 thaumic-bases|神秘基础学* - 官方版1.7.10¨thaumic-bases-unofficial|神秘基础学 (Thaumic Bases) - 非官方版1.12.2
 thaumic-bases|神秘基础学* - 官方版1.7.10¨thaumic-bases-unofficial|神秘基础学 (Thaumic Bases) - 非官方版1.12.2
@@ -486,7 +486,7 @@ warp-book|传送书*
 ex-compressum|压缩工具*|676165
 ex-compressum|压缩工具*|676165
 simply-jetpacks|简易喷气背包*
 simply-jetpacks|简易喷气背包*
 blood-arsenal|血液兵工厂*|958995
 blood-arsenal|血液兵工厂*|958995
-sim-u-kraft|模拟城市 (Sim-U-Kraft)
+
 superheroes-unlimited|超级英雄无限*|693397
 superheroes-unlimited|超级英雄无限*|693397
 oceancraft-mod|海洋世界 (Oceancraft)
 oceancraft-mod|海洋世界 (Oceancraft)
 
 
@@ -524,16 +524,16 @@ keeping-inventory||592605
 orespawn|矿石菌种 (OreSpawn)|464954
 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
 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
 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
 harvest-festival|丰收物语*|658968
 tough-as-nails|意志坚定*|849264
 tough-as-nails|意志坚定*|849264
 non-update|不再有更新*
 non-update|不再有更新*
 clayium|粘土工业*
 clayium|粘土工业*
 better-storage|更好的储存*
 better-storage|更好的储存*
 davincis-vessels|达芬奇之舟/达芬奇的船*|475292
 davincis-vessels|达芬奇之舟/达芬奇的船*|475292
-bspkrscore|bspkrs核心 (bspkrsCore)|430478¨bspkrscore-updated|bspkrs核心 (bspkrsCore)
+bspkrscore-updated|bspkrs核心 (bspkrsCore)¨bspkrscore|bspkrs核心 (bspkrsCore)|430478
 techguns|科技枪*|631578
 techguns|科技枪*|631578
 Iron-Backpacks|特殊背包*
 Iron-Backpacks|特殊背包*
 openmodularturrets|开放式炮台 (OpenModularTurrets)
 openmodularturrets|开放式炮台 (OpenModularTurrets)
@@ -544,7 +544,7 @@ dragonapi
 electricraft|电力工艺 (ElectriCraft)
 electricraft|电力工艺 (ElectriCraft)
 candycraft|糖果世界 (CandyCraft)|459723
 candycraft|糖果世界 (CandyCraft)|459723
 
 
-minefantasy2|我的幻想2 (MineFantasy2)
+
 
 
 rails-of-war|铁路战争 (Rails of War)
 rails-of-war|铁路战争 (Rails of War)
 actually-additions|实用拓展*|841281
 actually-additions|实用拓展*|841281
@@ -575,11 +575,11 @@ llibrary
 
 
 
 
 projecte|等价交换重制版 (ProjectE)|1109380
 projecte|等价交换重制版 (ProjectE)|1109380
-@redpower2-core|红石力量2 (RedPower2)|71843
 
 
+customthings|自定义物品 (CustomThings)
 
 
 automagy|自动化魔法*
 automagy|自动化魔法*
-
+minefantasy2|我的幻想2 (MineFantasy2)
 environmental-tech@|环境科技*|644622
 environmental-tech@|环境科技*|644622
 
 
 magic-cookies|魔法饼干*|710008
 magic-cookies|魔法饼干*|710008
@@ -592,7 +592,7 @@ another-one-bites-the-dust
 
 
 mine-mine-no-mi@|恶魔果实 (Mine Mine no Mi)|828931
 mine-mine-no-mi@|恶魔果实 (Mine Mine no Mi)|828931
 advanced-rocketry|高级火箭*|671760
 advanced-rocketry|高级火箭*|671760
-
+vics-modern-warfare-mod|维克的现代战争 (Vic's Modern Warfare Mod)|575936
 chance-cubes|机会方块*
 chance-cubes|机会方块*
 gun-customization
 gun-customization
 endercore@|末影核心 (EnderCore)
 endercore@|末影核心 (EnderCore)
@@ -616,15 +616,15 @@ dummycore¨dummycore-unofficial
 extra-utilities|更多实用设备*|868981
 extra-utilities|更多实用设备*|868981
 openmodslib|开放式模组库 (OpenModsLib)
 openmodslib|开放式模组库 (OpenModsLib)
 embers|余烬*
 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|怪物属性*
 mob-properties|怪物属性*
-ganys-surface|Gany的世界 (Gany's Surface)|450171
 
 
-ezstorage|EZ存储 (EZStorage)
 
 
+ezstorage|EZ存储 (EZStorage)
+ichunutil
 intangible
 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
 the-titans-mod|泰坦生物 (The Titans)|730072
 enchiridion
 enchiridion
 stimmedcow-nomorerecipeconflict|合成冲突消除 (NoMoreRecipeConflict)|237776
 stimmedcow-nomorerecipeconflict|合成冲突消除 (NoMoreRecipeConflict)|237776
@@ -666,7 +666,7 @@ valkyrielib@
 waila-harvestability||608318
 waila-harvestability||608318
 mixed-magic|混合魔法*
 mixed-magic|混合魔法*
 hwyla||648893
 hwyla||648893
-crafttweaker||769481
+crafttweaker@||769481
 
 
 real-time-chat-translation-mod|实时聊天翻译*
 real-time-chat-translation-mod|实时聊天翻译*
 
 
@@ -688,14 +688,14 @@ project-73926|超级合成框架 (Super Crafting Frame)
 akashic-tome|阿卡什宝典*|805606
 akashic-tome|阿卡什宝典*|805606
 morph-o-tool|变形工具 (Morph-o-Tool)
 morph-o-tool|变形工具 (Morph-o-Tool)
 decocraft@|装饰工艺*
 decocraft@|装饰工艺*
-refined-storage|精致存储*|1303245
+refined-storage@|精致存储*|1303245
 deep-resonance@|深度共振*
 deep-resonance@|深度共振*
 
 
 
 
 
 
 clockwork-phase|时之沙*
 clockwork-phase|时之沙*
 
 
-autoreglib
+autoreglib@
 roots|根源魔法*
 roots|根源魔法*
 guide-api|指南 (Guide-Api)
 guide-api|指南 (Guide-Api)
 fraghappy-reborn
 fraghappy-reborn
@@ -706,13 +706,13 @@ router-reborn|路由重生*
 coroutil
 coroutil
 vanilla-magic|原生魔法*
 vanilla-magic|原生魔法*
 taoism|御灵*|668355
 taoism|御灵*|668355
-essentialcraft-4-unofficial|源质魔法4非官方版 (EssentialCraft 4 Unofficial)
+
 celestial-craft|天体*
 celestial-craft|天体*
 lost-thaumaturgy|失落神秘 (Pengu's Lost Thaumaturgy)
 lost-thaumaturgy|失落神秘 (Pengu's Lost Thaumaturgy)
 compact-machines|更多空间/压缩空间*
 compact-machines|更多空间/压缩空间*
 
 
 ex-nihilo-omnia|无中生有-世间万物*|631637
 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|最终之剑:重生*
 the-last-sword-you-will-ever-need-reboot|最终之剑:重生*
 
 
 ftb-library-legacy-forge|FTB Library(旧版) (FTB Library (Forge) (Legacy))
 ftb-library-legacy-forge|FTB Library(旧版) (FTB Library (Forge) (Legacy))
@@ -786,7 +786,7 @@ snad|子沙*
 storage-drawers-extras
 storage-drawers-extras
 solar-flux-reborn|日光通量:重制版*
 solar-flux-reborn|日光通量:重制版*
 simple-void-world|简单虚空世界*|1089311
 simple-void-world|简单虚空世界*|1089311
-shetiphiancore¨shetiphiancore-fabric
+shetiphiancore-fabric
 shadowmc
 shadowmc
 scannable|扫描器*|692037
 scannable|扫描器*|692037
 rebornstorage|存储重置 (Reborn Storage)
 rebornstorage|存储重置 (Reborn Storage)
@@ -832,7 +832,7 @@ re-zero-kara-hajimeru-isekai-seikatsu|RE:从零开始的异世界 (Re: Zero kara
 minewatch||730508
 minewatch||730508
 thaumcraft-inventory-scanning|神秘时代物品栏扫描*
 thaumcraft-inventory-scanning|神秘时代物品栏扫描*
 cyclic|循环*|648189
 cyclic|循环*|648189
-chisel|凿子*|641383
+
 redstoneplusplus|红石++ (Redstone++)
 redstoneplusplus|红石++ (Redstone++)
 ctm|连接纹理 (ConnectedTexturesMod)
 ctm|连接纹理 (ConnectedTexturesMod)
 simulated-nights|夜晚模拟*
 simulated-nights|夜晚模拟*
@@ -870,7 +870,7 @@ omnis-core
 auto-paths|自动路径*
 auto-paths|自动路径*
 react
 react
 shared-resource-packs|共享资源包*
 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|皮革加工厂*
 Leather-Works|皮革加工厂*
 cyberware|机械改造*|618353
 cyberware|机械改造*|618353
 thermal-cultivation@|热力农业*
 thermal-cultivation@|热力农业*
@@ -911,9 +911,9 @@ back-to-eco|生态回归 (Back to Eco)
 world-of-warcraft|魔兽世界*
 world-of-warcraft|魔兽世界*
 the-witcher-adventure|猎魔人*
 the-witcher-adventure|猎魔人*
 fancy-battleaxes|华丽战斧*
 fancy-battleaxes|华丽战斧*
-zora-no-densha|Zora的电车 (Zora no Densha)|1085883
+
 roguelike-dungeons|冒险地牢*|300785
 roguelike-dungeons|冒险地牢*|300785
-first-aid|急救/医疗护理*|850687
+first-aid@firstaid|急救/医疗护理*|850687
 bonfires|篝火*|772983
 bonfires|篝火*|772983
 immersive-craft@|沉浸工艺*
 immersive-craft@|沉浸工艺*
 aroma1997core
 aroma1997core
@@ -935,7 +935,7 @@ improved-extraction|矿物获取改进*
 ecology-mod|生态*
 ecology-mod|生态*
 cakeisalie-mod|蛋糕是个谎言 (CakeIsALie)
 cakeisalie-mod|蛋糕是个谎言 (CakeIsALie)
 ex-nihilo-adscensio|无中生有:Adscensio*
 ex-nihilo-adscensio|无中生有:Adscensio*
-bookshelf
+bookshelf@bookshelf-lib
 
 
 random-things|随意作品*|938101
 random-things|随意作品*|938101
 
 
@@ -974,8 +974,8 @@ streams|溪流*
 blockcraftery|方块工匠*
 blockcraftery|方块工匠*
 bedrockores|基岩矿簇 (Bedrock Ores)|776822
 bedrockores|基岩矿簇 (Bedrock Ores)|776822
 chat-bubbles|聊天泡泡*
 chat-bubbles|聊天泡泡*
-refined-storage-addons|精致存储附属*
-foamfix-for-minecraft@foamfix|泡沫修复 (FoamFix)|678442
+refined-storage-addons@|精致存储附属*
+foamfix-optimization-mod@foamfix|泡沫修复 (FoamFix)|678442
 industrial-foregoing|工业先锋*|774112
 industrial-foregoing|工业先锋*|774112
 expanded-armory|扩展装备*
 expanded-armory|扩展装备*
 voxelmap|体素地图 (VoxelMap)|177583
 voxelmap|体素地图 (VoxelMap)|177583
@@ -1018,7 +1018,7 @@ wrapup
 nuclear-physics
 nuclear-physics
 wither-skeleton-tweaks|凋灵骷髅调整*
 wither-skeleton-tweaks|凋灵骷髅调整*
 heat-and-climate|热量与气候*
 heat-and-climate|热量与气候*
-customthings|自定义物品 (CustomThings)
+
 exgregilo|格雷矿筛 (ExGregilo)
 exgregilo|格雷矿筛 (ExGregilo)
 placebo
 placebo
 plants
 plants
@@ -1104,7 +1104,7 @@ netherutils
 bonsai-trees|盆栽*|785239
 bonsai-trees|盆栽*|785239
 
 
 
 
-misty-world|朦胧世界*
+misty-world@|朦胧世界*
 
 
 
 
 beta-kathairis|卡塞瑞斯 (Kathairis/Kether) - 1.12.2|865377¨kathairis-alive|卡塞瑞斯 (Kathairis/Kether) - 1.13+|865377
 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
 Requiem|安魂曲 (Requiem/Dissolution/Tartaros)|843736
 slashblade-in-tfc|古刀传 (Slash Blade in TFC)|789943
 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
 metamorph|变形*|723273
 reactioncraft-3-rebirth|世界反应3:重置 (Reactioncraft 3: Rebirth)
 reactioncraft-3-rebirth|世界反应3:重置 (Reactioncraft 3: Rebirth)
 pewter|魔法匠魂*
 pewter|魔法匠魂*
@@ -1160,7 +1160,7 @@ server-properties-for-lan|简单联机 (Server.Properties for LAN)|781904
 jaopcaadditions|JAOPCA附属 (JAOPCAAdditions)
 jaopcaadditions|JAOPCA附属 (JAOPCAAdditions)
 cloche-call
 cloche-call
 mouse-tweaks@|鼠标手势*|69677
 mouse-tweaks@|鼠标手势*|69677
-jaopcaagriculture|JAOPCA神秘农业附属 (JAOPCA Agriculture)
+jaopcaagriculture|JAOPCA神秘农业附属 (Just A Resource Generation Compatibility Attempt)
 sky-resources|空岛资源*|817623
 sky-resources|空岛资源*|817623
 hexxit-gear|Hexxit装备*
 hexxit-gear|Hexxit装备*
 
 
@@ -1331,7 +1331,7 @@ armoreablemobs|怪物装备自定义 (ArmoreableMobs)
 gem-buffs-infinite-potion-effects|宝石效果/增益宝石 (Gem Buffs)
 gem-buffs-infinite-potion-effects|宝石效果/增益宝石 (Gem Buffs)
 
 
 advanced-inventory
 advanced-inventory
-crafting-pillar-mod|合成柱*
+
 
 
 pollution-of-the-realms|环境污染/领域的污染 (Pollution of the Realms)|1410539
 pollution-of-the-realms|环境污染/领域的污染 (Pollution of the Realms)|1410539
 
 
@@ -1615,7 +1615,7 @@ integrated-terminals|集成终端*
 mekanism-tools@|通用机械工具*
 mekanism-tools@|通用机械工具*
 roboticparts|机械改造1.12+ (Robotic Parts)
 roboticparts|机械改造1.12+ (Robotic Parts)
 rewired
 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|垂直堆叠的世界*
 vertically-stacked-dimensions|垂直堆叠的世界*
 
 
 malisisswitches
 malisisswitches
@@ -1774,12 +1774,12 @@ up-and-down-and-all-around|颠倒世界*
 lost-souls@|失落的灵魂*
 lost-souls@|失落的灵魂*
 intelligent-energistics|智能能源*
 intelligent-energistics|智能能源*
 ender-compass|末影指南针*|785069
 ender-compass|末影指南针*|785069
-ic2-tweaker
+
 thaumic-gadgets|神秘宝具*
 thaumic-gadgets|神秘宝具*
 multi-mine|多人开采*
 multi-mine|多人开采*
 extra-foam-for-liteloader|Liteloader修复 (Extra Foam For LiteLoader)|982739
 extra-foam-for-liteloader|Liteloader修复 (Extra Foam For LiteLoader)|982739
 embersified|余烬能量转换*
 embersified|余烬能量转换*
-ichunutil
+
 thaumic-grid|神秘网络*
 thaumic-grid|神秘网络*
 back-tools|工具后置/后背工具展示*
 back-tools|工具后置/后背工具展示*
 mapwriter-2|地图作者2*|680409
 mapwriter-2|地图作者2*|680409
@@ -1791,13 +1791,13 @@ dm2|龙骑士2 (Dragon Mounts 2)
 tiquality
 tiquality
 loot-capacitor-tooltips|电容信息显示*|888624
 loot-capacitor-tooltips|电容信息显示*|888624
 classic-bars|经典状态条*
 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|地表矿石*
 overground-ores|地表矿石*
 touhou-little-maid|车万女仆*|882845
 touhou-little-maid|车万女仆*|882845
 
 
 eureka
 eureka
 lazy-ae2|懒人AE2 (Lazy AE2)
 lazy-ae2|懒人AE2 (Lazy AE2)
-libnine
+
 equaldragons
 equaldragons
 berries
 berries
 infinity-gauntlet-mod|无限手套 (Infinity Gauntlet)
 infinity-gauntlet-mod|无限手套 (Infinity Gauntlet)
@@ -2101,7 +2101,7 @@ spatialcompat|空间兼容 (SpatialCompat)
 extended-reach
 extended-reach
 no-recipe-book|没有配方书*
 no-recipe-book|没有配方书*
 darknesslib
 darknesslib
-terrafirmacraft|群峦传说:次世代 (TerraFirmaCraft: The Next Generation)|887732¨tfcraft|群峦传说 (TerraFirmaCraft)|887732
+tfcraft|群峦传说 (TerraFirmaCraft)|887732¨terrafirmacraft|群峦传说:次世代 (TerraFirmaCraft: The Next Generation)|887732
 redstone-control|红石控制*
 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
 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)
 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)
 mysterious-mountain-lib|妖怪之山通用库 (MysteriousMountainLib)
 
 
 
 
-
+kitchen-mod|厨房 (The Kitchen Mod)|295019
 
 
 baubles-reborn|饰品栏重置版*
 baubles-reborn|饰品栏重置版*
 pams-brewcraft|潘马斯酿造工艺 (Pam's Brewcraft)
 pams-brewcraft|潘马斯酿造工艺 (Pam's Brewcraft)
@@ -2415,7 +2415,7 @@ chunk-in-a-globe|球内区块*
 dragon-mounts-legacy|龙骑士重置版 (Dragon Mounts: Legacy)
 dragon-mounts-legacy|龙骑士重置版 (Dragon Mounts: Legacy)
 fruit-trees|果树 🍊 (Fruit Trees 🍊)|976516
 fruit-trees|果树 🍊 (Fruit Trees 🍊)|976516
 journey-into-the-light-mod|光明之旅·神之本质 (Journey Into the Light · Essence of the Gods)|1128195
 journey-into-the-light-mod|光明之旅·神之本质 (Journey Into the Light · Essence of the Gods)|1128195
-kitchen-mod|厨房 (The Kitchen Mod)|295019
+
 more-dungeons|更多地牢*
 more-dungeons|更多地牢*
 voyage
 voyage
 idealland|理想境*|1051328
 idealland|理想境*|1051328
@@ -2464,7 +2464,7 @@ moddirector
 function-api
 function-api
 targetingapi
 targetingapi
 re-targetingapi
 re-targetingapi
-
+libnine
 dominionlib
 dominionlib
 
 
 endergetic|末地拓展 (The Endergetic Expansion)
 endergetic|末地拓展 (The Endergetic Expansion)
@@ -2784,7 +2784,7 @@ mekanism-fluxified|“通量”机械*
 stepup|平滑自动上坡 (StepUp)
 stepup|平滑自动上坡 (StepUp)
 @sodium|钠 (Sodium)|1079347¨sodium|钠*|1079347
 @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)
 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
 terrible-chest|可怕的箱子/可怖箱子*|922554
 spartan-shields|斯巴达之盾*|672959
 spartan-shields|斯巴达之盾*|672959
@@ -2856,7 +2856,7 @@ jei-enchantment-info|JEI附魔信息 (JEI Enchantment Info)
 
 
 project-red-fabrication|红石计划:制作 (Project Red - Fabrication)
 project-red-fabrication|红石计划:制作 (Project Red - Fabrication)
 project-red-integration|红石计划:集成 (Project Red - Integration)
 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-world|红石计划:探索 (Project Red - World)
 project-red-compat|红石计划:兼容性 (Project Red - Compat)
 project-red-compat|红石计划:兼容性 (Project Red - Compat)
 canvas-renderer|Canvas渲染器*|1211675¨@canvas|Canvas渲染器 (Canvas Renderer)|1211675
 canvas-renderer|Canvas渲染器*|1211675¨@canvas|Canvas渲染器 (Canvas Renderer)|1211675
@@ -2917,7 +2917,7 @@ upgrade-aquatic|碧海新生*|962459
 tfc-ambiental
 tfc-ambiental
 macaws-trapdoors|Macaw的活板门 (Macaw's Trapdoors)
 macaws-trapdoors|Macaw的活板门 (Macaw's Trapdoors)
 zenutil
 zenutil
-
+instant-bubble-studio|瞬息气泡工坊*|1103368
 better-beacon-effect|更好的信标效果*
 better-beacon-effect|更好的信标效果*
 netherite-horse-armor|下界合金马铠*
 netherite-horse-armor|下界合金马铠*
 tacocraft-fabric
 tacocraft-fabric
@@ -2925,7 +2925,7 @@ lootr¨lootr-fabric
 biolib@bio-library
 biolib@bio-library
 scavenge¨documentation|Scavenge - WIKI: 模组文档(1.10.2-1.12.2)
 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)
 pams-harvestcraft-2-food-extended|潘马斯农场2 - 食物拓展 (Pam's HarvestCraft 2 - Food Extended)
 
 
@@ -2955,7 +2955,7 @@ lambdynamiclights@|Lambda的动态光源 (LambDynamicLights)|1082100
 realistic-torches|真实火把*|566201
 realistic-torches|真实火把*|566201
 
 
 durability-viewer|耐久显示器*|1086168
 durability-viewer|耐久显示器*|1086168
-netherlicious||1104913
+
 
 
 
 
 ground-item-highlighting|地面物品高光显示*|1084931
 ground-item-highlighting|地面物品高光显示*|1084931
@@ -2976,7 +2976,7 @@ packagedexcrafting|封包合成拓展 (PackagedExCrafting)
 
 
 compactstorage|紧凑存储 (compactstorage)
 compactstorage|紧凑存储 (compactstorage)
 useful-backpacks|实用背包*
 useful-backpacks|实用背包*
-conquest-reforged|征服者 (ConquestReforged)|1431287
+@conquest-reforged|征服者 (ConquestReforged)|1431287
 steelseries-gamesense
 steelseries-gamesense
 pizzaatimes-timber-mod
 pizzaatimes-timber-mod
 tellme
 tellme
@@ -3006,7 +3006,7 @@ server-translations
 image2map@
 image2map@
 corpse@|遗体*
 corpse@|遗体*
 ring-of-the-miner-fabric|矿工戒指 (Ring of the Miner) - Fabric¨ring-of-the-miner-forge|矿工戒指 (Ring of the Miner) - Forge
 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|烹饪锅*
 crock-pot|烹饪锅*
 
 
-heaven-earth-ring|乾坤戒指*|1113665
+
 
 
 
 
 
 
@@ -3059,7 +3059,7 @@ entity-culling|实体渲染机制优化*
 hammerandvil|锻造锤与锤锻台 (HammerAndVil)
 hammerandvil|锻造锤与锤锻台 (HammerAndVil)
 
 
 simply-backpacks|简易背包*
 simply-backpacks|简易背包*
-mermod-fabric¨mermod-forge|Mermod / Mermaid Tail Mod - 1.19.2~1.18.2 Forge版本
+@mermod
 better-placement|更好的放置*|730136
 better-placement|更好的放置*|730136
 fat-cat|大资本家*
 fat-cat|大资本家*
 waystones2waypoints|传送石碑路径点 (Waystones2Waypoints)
 waystones2waypoints|传送石碑路径点 (Waystones2Waypoints)
@@ -3190,7 +3190,7 @@ ambience-extras|环境音乐:附加 (Ambience - Extras)
 additions-mod
 additions-mod
 extracpus|更多CPU (Extra CPUs)
 extracpus|更多CPU (Extra CPUs)
 extra-crafting-storage
 extra-crafting-storage
-@conquest-reforged|征服者 (ConquestReforged)|1431287
+conquest-reforged|征服者 (ConquestReforged)|1431287
 underground-city-engineering@|地下城市工程*|943842
 underground-city-engineering@|地下城市工程*|943842
 essentials
 essentials
 soul-shards-respawn
 soul-shards-respawn
@@ -3246,7 +3246,7 @@ randompatches-integration
 bubble-column-elevator-backport|1.13水域机制 (1.13 Water Mechanics)
 bubble-column-elevator-backport|1.13水域机制 (1.13 Water Mechanics)
 basic-nether-ores|基础下界矿石*|1136294
 basic-nether-ores|基础下界矿石*|1136294
 nuclearcraft-reactor-builder|核电工艺反应堆建造机 (NuclearCraft Reactor Builder)
 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
 eki-lib
 
 
 nodami|伤害免疫机制移除 (No Damage Immunity)
 nodami|伤害免疫机制移除 (No Damage Immunity)
@@ -3750,14 +3750,14 @@ necromancy
 library-of-exile
 library-of-exile
 
 
 variegated
 variegated
-
+chest-cavity@|胸腔*
 extended-note-block|音符盒扩展 (Extended Noteblocks)
 extended-note-block|音符盒扩展 (Extended Noteblocks)
 hbms-nuclear-tech-mod-reloaded|HBM的核科技重制版 (Hbm's Nuclear Tech Mod Reloaded)
 hbms-nuclear-tech-mod-reloaded|HBM的核科技重制版 (Hbm's Nuclear Tech Mod Reloaded)
 loading-timer@|加载时长显示*
 loading-timer@|加载时长显示*
 ocrocketry
 ocrocketry
 chime-fabric@chime
 chime-fabric@chime
 witherite@|凋灵合金*
 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)
 astromine-core|天体矿工:核心 (Astromine: Core)
 dungeondq
 dungeondq
 dynamic-trees-integrated-dynamics|动态的树:动态联合/集成动力附属 (Dynamic Trees - Integrated Dynamics)
 dynamic-trees-integrated-dynamics|动态的树:动态联合/集成动力附属 (Dynamic Trees - Integrated Dynamics)
@@ -4085,7 +4085,7 @@ ticktock@servertick
 
 
 fps-reducre-for-fabric|FPS减速器(Fabric) (FPS Reducer (For Fabric))
 fps-reducre-for-fabric|FPS减速器(Fabric) (FPS Reducer (For Fabric))
 art-artillery|艺术?火炮! (Art? Artillery!)
 art-artillery|艺术?火炮! (Art? Artillery!)
-minemenu-refabricated@minemenufabric|我的菜单 Fabric 版 (MineMenuFabric)
+minemenufabric@|我的菜单 Fabric 版 (MineMenuFabric)
 @tabtps
 @tabtps
 @afkpeace¨afkpeace
 @afkpeace¨afkpeace
 
 
@@ -4112,7 +4112,7 @@ village-employment|村庄就业*
 
 
 deathlog@|死亡日志*
 deathlog@|死亡日志*
 automatic-elytra|自动鞘翅*
 automatic-elytra|自动鞘翅*
-
+axolotl-bucket-fix|美西螈桶修复*
 xks-decoration|XK的装饰 (XK's Deco)|1214362
 xks-decoration|XK的装饰 (XK's Deco)|1214362
 endgame-materials-addons|更多终局材料*
 endgame-materials-addons|更多终局材料*
 ravage-and-cabbage
 ravage-and-cabbage
@@ -4556,7 +4556,7 @@ multi-world
 
 
 terralith¨@terralith
 terralith¨@terralith
 overworld-two¨overworld-two-forge
 overworld-two¨overworld-two-forge
-
+xl-packets|XL数据包 (XL Packets) - Forge¨xl-packets-fabric|XL数据包 (XL Packets) - Fabric
 rftools-storage@|RF工具:存储 (RFTools Storage)
 rftools-storage@|RF工具:存储 (RFTools Storage)
 rftools-builder@|RF工具:建造机 (RFTools Builder)
 rftools-builder@|RF工具:建造机 (RFTools Builder)
 
 
@@ -4724,7 +4724,7 @@ desolation-forge|荒芜 (Desolation) - Forge¨desolation|荒芜* - Fabric
 ma-astral
 ma-astral
 zaraklib
 zaraklib
 chipped@
 chipped@
-chest-cavity@|胸腔*
+
 mending-for-1-7-10|给老版本的经验修补 (Mending for Older Version)
 mending-for-1-7-10|给老版本的经验修补 (Mending for Older Version)
 unreachable-fantasy-mod
 unreachable-fantasy-mod
 ai-reducer|AI减速器 (AI Reducer)
 ai-reducer|AI减速器 (AI Reducer)
@@ -4963,7 +4963,7 @@ one-block-forge
 human-reborn
 human-reborn
 @addadd|额外扩展 (Additional Additions)|1427321¨additional-additions|额外扩展* - Fabric|1427321¨additional-additions-forge|额外扩展 (Additional Additions) - Forge|1427321
 @addadd|额外扩展 (Additional Additions)|1427321¨additional-additions|额外扩展* - Fabric|1427321¨additional-additions-forge|额外扩展 (Additional Additions) - Forge|1427321
 earlygame@
 earlygame@
-happiness-is-a-warm-gun@
+
 
 
 kvc-blocks|蛋挞君的原版补全方块篇 (KVCBlocks)|1239534
 kvc-blocks|蛋挞君的原版补全方块篇 (KVCBlocks)|1239534
 enigtech2-util
 enigtech2-util
@@ -5073,7 +5073,7 @@ food-effects
 item-borders|物品边框* - Forge¨item-borders-fabric|物品边框 (Item Borders) - Fabric
 item-borders|物品边框* - Forge¨item-borders-fabric|物品边框 (Item Borders) - Fabric
 iceberg|冰山* - Forge¨iceberg-fabric|冰山 (Iceberg) - Fabric¨@iceberg|冰山 (Iceberg)
 iceberg|冰山* - Forge¨iceberg-fabric|冰山 (Iceberg) - Fabric¨@iceberg|冰山 (Iceberg)
 
 
-
+worldeditcui-forge-edition-3
 enhanced-farming@|增强农业*
 enhanced-farming@|增强农业*
 
 
 
 
@@ -5254,7 +5254,7 @@ durability101@
 ceramic-shears|陶瓷剪刀*
 ceramic-shears|陶瓷剪刀*
 torch-bandolier|火把弹药袋*
 torch-bandolier|火把弹药袋*
 rapid-leaf-decay
 rapid-leaf-decay
-
+netherite-horse-armor-mod|下界合金马铠*
 ambient-environment
 ambient-environment
 glass-cutter
 glass-cutter
 dicemc-tiered-armors
 dicemc-tiered-armors
@@ -5420,7 +5420,7 @@ compass-manhunt
 exlines-furniture
 exlines-furniture
 tinkers-archery|工匠箭术 (Tinkers' Archery)
 tinkers-archery|工匠箭术 (Tinkers' Archery)
 designer-modeling|设计师建模*
 designer-modeling|设计师建模*
-netherite-horse-armor-mod|下界合金马铠*
+
 netherite-horse-armor-fabric|下界合金马铠 (Netherite Horse Armor)
 netherite-horse-armor-fabric|下界合金马铠 (Netherite Horse Armor)
 netherite-elytra|下界合金鞘翅*
 netherite-elytra|下界合金鞘翅*
 
 
@@ -5952,7 +5952,7 @@ fabricskyboxes@
 @fvt
 @fvt
 blue_skies_tcon
 blue_skies_tcon
 tdv-tweaks
 tdv-tweaks
-
+ic2-tweaker
 picture-in-picture-pip
 picture-in-picture-pip
 guilt-trip
 guilt-trip
 parcool|跑酷! (ParCool!)
 parcool|跑酷! (ParCool!)
@@ -6042,7 +6042,7 @@ the-one-probe-fabric
 gehenna-super-spooky-nether-overhaul-for-forge
 gehenna-super-spooky-nether-overhaul-for-forge
 draconicminus
 draconicminus
 hygiene
 hygiene
-worldeditcui-forge-edition-3
+
 external-view-reload
 external-view-reload
 fluid-drawers-legacy|储液抽屉:遗产*
 fluid-drawers-legacy|储液抽屉:遗产*
 my-server-is-compatible
 my-server-is-compatible
@@ -6659,7 +6659,7 @@ astemirs-fireflies
 ender-crop¨@ender-crop
 ender-crop¨@ender-crop
 modupdater
 modupdater
 fivehead
 fivehead
-axolotl-bucket-fix|美西螈桶修复*
+
 attained-drops|可种植掉落物*
 attained-drops|可种植掉落物*
 attained-drops-2|可种植掉落物2*
 attained-drops-2|可种植掉落物2*
 
 
@@ -7797,7 +7797,7 @@ hungertweaker
 knob-control
 knob-control
 daedalus-labyrinth
 daedalus-labyrinth
 tip-the-gui|界面尺寸设置优化 (GUI Scale)
 tip-the-gui|界面尺寸设置优化 (GUI Scale)
-copper-overhaul
+
 epic-fight-nbt-integration
 epic-fight-nbt-integration
 login-protection|登录保护*
 login-protection|登录保护*
 swem
 swem
@@ -8252,7 +8252,7 @@ super-secret-settings
 @plumeconfig
 @plumeconfig
 geophilic@
 geophilic@
 critter-fights
 critter-fights
-hypothermic@
+
 nosecrets|不秘密 (NoSecrets)
 nosecrets|不秘密 (NoSecrets)
 extra-creeper-types
 extra-creeper-types
 loot-casket
 loot-casket
@@ -8759,7 +8759,7 @@ otg-skylands
 fxs-rail-optimization@rail-optimization
 fxs-rail-optimization@rail-optimization
 morejs@
 morejs@
 
 
-@moresnowballls|更多雪球 (MoreSnowballs)
+
 valkyrien-skies-control
 valkyrien-skies-control
 the-end-expanded-mod-ice-and-fire-addon
 the-end-expanded-mod-ice-and-fire-addon
 @vault-patcher|保险库补丁 (Vault Patcher)
 @vault-patcher|保险库补丁 (Vault Patcher)
@@ -8822,7 +8822,7 @@ dawn-to-dusk
 open-tower-defence|开放式塔防*
 open-tower-defence|开放式塔防*
 mtr-station-decoration@station-decoration
 mtr-station-decoration@station-decoration
 perviaminvenire
 perviaminvenire
-
+hypothermic@
 silverbirch@silver-birch
 silverbirch@silver-birch
 
 
 
 
@@ -9078,7 +9078,7 @@ commandspy
 slimyfloor
 slimyfloor
 daily-dad@|每日冷笑话*¨daily-dad-server@|每日冷笑话 (Daily Dad) - Daily Dad - Server
 daily-dad@|每日冷笑话*¨daily-dad-server@|每日冷笑话 (Daily Dad) - Daily Dad - Server
 good-ending
 good-ending
-xl-packets|XL数据包 (XL Packets) - Forge¨xl-packets-fabric|XL数据包 (XL Packets) - Fabric
+
 creeper-nuggets
 creeper-nuggets
 coherent-villages
 coherent-villages
 
 
@@ -9204,7 +9204,7 @@ snowballing
 winter-essentials
 winter-essentials
 magnesium-rubidium-extra-potato-editon|镁/铷附属:土豆版 (Magnesium/Rubidium Extra: Potato Editon)
 magnesium-rubidium-extra-potato-editon|镁/铷附属:土豆版 (Magnesium/Rubidium Extra: Potato Editon)
 @realistic-cruelty|真实残忍 (Realistic Cruelty)
 @realistic-cruelty|真实残忍 (Realistic Cruelty)
-trapper-pelts
+
 workshops-of-doom
 workshops-of-doom
 create-mechanical-spawner|机械动力:动力刷怪笼*
 create-mechanical-spawner|机械动力:动力刷怪笼*
 
 
@@ -9849,7 +9849,7 @@ enchancement
 
 
 useful-spyglass@
 useful-spyglass@
 @illagers-love-armor
 @illagers-love-armor
-
+@moresnowballls|更多雪球 (MoreSnowballs)
 befriendmobs-days-with-monster-girls|友好化生物:与怪物娘的日常 (BefriendMobs: Days with Monster Girls)
 befriendmobs-days-with-monster-girls|友好化生物:与怪物娘的日常 (BefriendMobs: Days with Monster Girls)
 launchgui
 launchgui
 villager-armor-layer@|村民盔甲显示*
 villager-armor-layer@|村民盔甲显示*
@@ -9866,7 +9866,7 @@ extra-trades
 blood-particles-mod
 blood-particles-mod
 @two-weeks-hud
 @two-weeks-hud
 fazcraft-mod
 fazcraft-mod
-mod-menu-no-more@changelog
+mod-menu-no-more@
 @slime-craft
 @slime-craft
 way2wayfabric@
 way2wayfabric@
 cloche-profit-peripheral
 cloche-profit-peripheral
@@ -9960,7 +9960,7 @@ enderio-unofficial|末影接口非官方版 (EnderIO Unofficial)
 
 
 dimensional-gravity
 dimensional-gravity
 
 
-
+happiness-is-a-warm-gun@
 bring-color-to-my-skies
 bring-color-to-my-skies
 portal-dungeons
 portal-dungeons
 
 
@@ -10217,7 +10217,7 @@ boat-break-fix
 advanced-movement
 advanced-movement
 totem-overhaul
 totem-overhaul
 mobs-no-render@
 mobs-no-render@
-
+copper-overhaul
 covenants-choose-your-purpose
 covenants-choose-your-purpose
 insatiable
 insatiable
 
 
@@ -10466,4 +10466,735 @@ chunk-sending-forge-fabric
 overworld-mirror
 overworld-mirror
 @toggleable-sonic-boom
 @toggleable-sonic-boom
 quicksort@
 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