1
0
龙腾猫跃 3 жил өмнө
parent
commit
6dc8789ac0
32 өөрчлөгдсөн 2034 нэмэгдсэн , 1242 устгасан
  1. 13 7
      Plain Craft Launcher 2/Controls/MyCard.vb
  2. 30 14
      Plain Craft Launcher 2/FormMain.xaml.vb
  3. BIN
      Plain Craft Launcher 2/Images/Icons/NoIcon.png
  4. 7 36
      Plain Craft Launcher 2/Modules/Base/ModBase.vb
  5. 1 1
      Plain Craft Launcher 2/Modules/Base/ModLoader.vb
  6. 3 3
      Plain Craft Launcher 2/Modules/Base/ModNet.vb
  7. 1216 0
      Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
  8. 10 8
      Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
  9. 7 710
      Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
  10. 4 4
      Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
  11. 10 14
      Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
  12. 191 36
      Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
  13. 3 3
      Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml
  14. 22 14
      Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb
  15. 2 2
      Plain Craft Launcher 2/My Project/AssemblyInfo.vb
  16. 10 10
      Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
  17. 4 4
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml
  18. 91 92
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb
  19. 39 24
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb
  20. 2 2
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml
  21. 2 0
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb
  22. 41 24
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml
  23. 70 26
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb
  24. 34 21
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml
  25. 72 38
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml.vb
  26. 3 3
      Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb
  27. 1 1
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb
  28. 2 2
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
  29. 1 1
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb
  30. 7 6
      Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
  31. 136 136
      Plain Craft Launcher 2/Resources/ModData.txt
  32. BIN
      Plain Craft Launcher 2/Resources/java-wrapper.jar

+ 13 - 7
Plain Craft Launcher 2/Controls/MyCard.vb

@@ -96,8 +96,8 @@
                                                                                      Return VersionSortBoolean(Left.Version, Right.Version)
                                                                                      Return VersionSortBoolean(Left.Version, Right.Version)
                                                                                  End Function)
                                                                                  End Function)
             Case 8, 9
             Case 8, 9
-                Stack.Tag = Sort(CType(Stack.Tag, List(Of DlCfFile)), Function(Left As DlCfFile, Right As DlCfFile) As Boolean
-                                                                          Return Left.Date > Right.Date
+                Stack.Tag = Sort(CType(Stack.Tag, List(Of CompFile)), Function(Left As CompFile, Right As CompFile) As Boolean
+                                                                          Return Left.ReleaseDate > Right.ReleaseDate
                                                                       End Function)
                                                                       End Function)
         End Select
         End Select
         '控件转换
         '控件转换
@@ -113,7 +113,7 @@
             Case 6
             Case 6
                 ForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf ForgeSave_Click, True)
                 ForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf ForgeSave_Click, True)
             Case 8
             Case 8
-                DlCfFilesPreload(Stack, Stack.Tag, AddressOf FrmDownloadMod.ProjectClick)
+                CompFilesCardPreload(Stack, Stack.Tag, AddressOf FrmDownloadMod.ProjectClick)
         End Select
         End Select
         '实现控件虚拟化
         '实现控件虚拟化
         For Each Data As Object In Stack.Tag
         For Each Data As Object In Stack.Tag
@@ -134,9 +134,9 @@
                                                                     FrmDownloadInstall.MinecraftSelected(sender, e)
                                                                     FrmDownloadInstall.MinecraftSelected(sender, e)
                                                                 End Sub, False))
                                                                 End Sub, False))
                 Case 8
                 Case 8
-                    Stack.Children.Add(CType(Data, DlCfFile).ToListItem(AddressOf FrmDownloadCfDetail.Save_Click))
+                    Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Save_Click))
                 Case 9
                 Case 9
-                    Stack.Children.Add(CType(Data, DlCfFile).ToListItem(AddressOf FrmDownloadCfDetail.Install_Click, AddressOf FrmDownloadCfDetail.Save_Click))
+                    Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Install_Click, AddressOf FrmDownloadCompDetail.Save_Click))
                 Case 10
                 Case 10
                     Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf LiteLoaderSave_Click, True))
                     Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf LiteLoaderSave_Click, True))
                 Case 11
                 Case 11
@@ -173,7 +173,11 @@
         AniStart(AniList, "MyCard Mouse " & Uuid)
         AniStart(AniList, "MyCard Mouse " & Uuid)
     End Sub
     End Sub
 
 
-    '高度改变动画
+#Region "高度改变动画"
+
+    ''' <summary>
+    ''' 是否启用高度改变动画。
+    ''' </summary>
     Public Property UseAnimation As Boolean = True
     Public Property UseAnimation As Boolean = True
     Private IsHeightAnimating As Boolean = False
     Private IsHeightAnimating As Boolean = False
     Private ActualUsedHeight As Double '回滚实际高度(例如 NaN)
     Private ActualUsedHeight As Double '回滚实际高度(例如 NaN)
@@ -221,6 +225,8 @@
         IsHeightAnimating = False
         IsHeightAnimating = False
     End Sub
     End Sub
 
 
+#End Region
+
     '折叠
     '折叠
     '若设置了 CanSwap,或 SwapControl 不为空,则判定为会进行折叠
     '若设置了 CanSwap,或 SwapControl 不为空,则判定为会进行折叠
     '这是因为不能直接在 XAML 中设置 SwapControl
     '这是因为不能直接在 XAML 中设置 SwapControl
@@ -237,7 +243,7 @@
         Get
         Get
             Return _IsSwaped
             Return _IsSwaped
         End Get
         End Get
-        Set(ByVal value As Boolean)
+        Set(value As Boolean)
             If _IsSwaped = value Then Exit Property
             If _IsSwaped = value Then Exit Property
             _IsSwaped = value
             _IsSwaped = value
             If SwapControl Is Nothing Then Exit Property
             If SwapControl Is Nothing Then Exit Property

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

@@ -126,6 +126,13 @@ Public Class FormMain
         '3:BUG+ IMP* FEAT-
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '2:BUG* IMP-
         '1:BUG-
         '1:BUG-
+        If LastVersion < 283 Then 'Snapshot 2.5.0
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "支持搜索、下载 Modrinth 中的 Mod 与整合包"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "资源搜索页面支持翻页"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持安装 Modrinth 整合包"))
+            FeatureCount += 5
+            BugCount += 5
+        End If
         If LastVersion < 282 Then 'Snapshot 2.4.9
         If LastVersion < 282 Then 'Snapshot 2.4.9
             FeatureCount += 1
             FeatureCount += 1
             BugCount += 5
             BugCount += 5
@@ -507,7 +514,7 @@ Reopen:
             Dim UnlockedTheme As New List(Of String) From {"2"}
             Dim UnlockedTheme As New List(Of String) From {"2"}
             UnlockedTheme.AddRange(New List(Of String)(Setup.Get("UiLauncherThemeHide").ToString.Split("|")))
             UnlockedTheme.AddRange(New List(Of String)(Setup.Get("UiLauncherThemeHide").ToString.Split("|")))
             UnlockedTheme.AddRange(New List(Of String)(Setup.Get("UiLauncherThemeHide2").ToString.Split("|")))
             UnlockedTheme.AddRange(New List(Of String)(Setup.Get("UiLauncherThemeHide2").ToString.Split("|")))
-            Setup.Set("UiLauncherThemeHide2", Join(ArrayNoDouble(UnlockedTheme), "|"))
+            Setup.Set("UiLauncherThemeHide2", Join(UnlockedTheme.Distinct.ToList, "|"))
         End If
         End If
         '重置欧皇彩
         '重置欧皇彩
         If LastVersionCode <= 115 AndAlso Setup.Get("UiLauncherThemeHide2").ToString.Split("|").Contains("13") Then
         If LastVersionCode <= 115 AndAlso Setup.Get("UiLauncherThemeHide2").ToString.Split("|").Contains("13") Then
@@ -866,7 +873,7 @@ Install:
                                        Exit Sub
                                        Exit Sub
                                    End If
                                    End If
                                    '安装整合包
                                    '安装整合包
-                                   If {"zip", "rar"}.Contains(Extension) Then '部分压缩包是 zip 格式但后缀为 rar,总之试一试
+                                   If {"zip", "rar", "mrpack"}.Contains(Extension) Then '部分压缩包是 zip 格式但后缀为 rar,总之试一试
                                        Log("[System] 文件为压缩包,尝试作为整合包安装")
                                        Log("[System] 文件为压缩包,尝试作为整合包安装")
                                        If ModpackInstall(FilePath, ShowHint:=False) Then Exit Sub
                                        If ModpackInstall(FilePath, ShowHint:=False) Then Exit Sub
                                    End If
                                    End If
@@ -1010,9 +1017,9 @@ Install:
         ''' </summary>
         ''' </summary>
         VersionSetup
         VersionSetup
         ''' <summary>
         ''' <summary>
-        ''' CurseForge 工程详情。这是一个副页面。
+        ''' 资源工程详情。这是一个副页面。
         ''' </summary>
         ''' </summary>
-        CfDetail
+        CompDetail
         ''' <summary>
         ''' <summary>
         ''' 帮助详情。这是一个副页面。
         ''' 帮助详情。这是一个副页面。
         ''' </summary>
         ''' </summary>
@@ -1059,13 +1066,20 @@ Install:
                 Return "下载管理"
                 Return "下载管理"
             Case PageType.VersionSetup
             Case PageType.VersionSetup
                 Return "版本设置 - " & If(PageVersionLeft.Version Is Nothing, "未知版本", PageVersionLeft.Version.Name)
                 Return "版本设置 - " & If(PageVersionLeft.Version Is Nothing, "未知版本", PageVersionLeft.Version.Name)
-            Case PageType.CfDetail
+            Case PageType.CompDetail
                 If Stack.Additional Is Nothing Then
                 If Stack.Additional Is Nothing Then
-                    Log("[Control] CurseForge 工程详情页面未提供关键项", LogLevel.Feedback)
+                    Log("[Control] 资源工程详情页面未提供关键项", LogLevel.Feedback)
                     Return "未知页面"
                     Return "未知页面"
                 Else
                 Else
-                    Dim Project As DlCfProject = Stack.Additional
-                    Return If(Project.IsModPack, "整合包下载 - ", "Mod 下载 - ") & Project.ChineseName
+                    Dim Project As CompProject = Stack.Additional
+                    Select Case Project.Type
+                        Case CompType.Mod
+                            Return "Mod 下载 - " & Project.TranslatedName
+                        Case CompType.ModPack
+                            Return "整合包下载 - " & Project.TranslatedName
+                        Case Else 'CompType.ResourcePack
+                            Return "资源包下载 - " & Project.TranslatedName
+                    End Select
                 End If
                 End If
             Case PageType.HelpDetail
             Case PageType.HelpDetail
                 If Stack.Additional Is Nothing Then
                 If Stack.Additional Is Nothing Then
@@ -1303,9 +1317,9 @@ Install:
                 Case PageType.VersionSetup '版本设置
                 Case PageType.VersionSetup '版本设置
                     If FrmVersionLeft Is Nothing Then FrmVersionLeft = New PageVersionLeft
                     If FrmVersionLeft Is Nothing Then FrmVersionLeft = New PageVersionLeft
                     PageChangeAnim(FrmVersionLeft, FrmVersionLeft.PageGet(SubType))
                     PageChangeAnim(FrmVersionLeft, FrmVersionLeft.PageGet(SubType))
-                Case PageType.CfDetail 'Mod 信息
-                    If FrmDownloadCfDetail Is Nothing Then FrmDownloadCfDetail = New PageDownloadCfDetail
-                    PageChangeAnim(New MyPageLeft, FrmDownloadCfDetail)
+                Case PageType.CompDetail 'Mod 信息
+                    If FrmDownloadCompDetail Is Nothing Then FrmDownloadCompDetail = New PageDownloadCompDetail
+                    PageChangeAnim(New MyPageLeft, FrmDownloadCompDetail)
                 Case PageType.HelpDetail '帮助详情
                 Case PageType.HelpDetail '帮助详情
                     PageChangeAnim(New MyPageLeft, Stack.Additional(1))
                     PageChangeAnim(New MyPageLeft, Stack.Additional(1))
             End Select
             End Select
@@ -1505,14 +1519,16 @@ Install:
         Return HasRunningMinecraft
         Return HasRunningMinecraft
     End Function
     End Function
 
 
-    '返回顶部
-    Private Sub BtnExtraBack_Click(sender As Object, e As EventArgs) Handles BtnExtraBack.Click
+    ''' <summary>
+    ''' 返回顶部。
+    ''' </summary>
+    Public Sub BackToTop() Handles BtnExtraBack.Click
         Dim RealScroll As MyScrollViewer = BtnExtraBack_GetRealChild()
         Dim RealScroll As MyScrollViewer = BtnExtraBack_GetRealChild()
         RealScroll.PerformVerticalOffsetDelta(-RealScroll.VerticalOffset)
         RealScroll.PerformVerticalOffsetDelta(-RealScroll.VerticalOffset)
     End Sub
     End Sub
     Private Function BtnExtraBack_ShowCheck() As Boolean
     Private Function BtnExtraBack_ShowCheck() As Boolean
         Dim RealScroll As MyScrollViewer = BtnExtraBack_GetRealChild()
         Dim RealScroll As MyScrollViewer = BtnExtraBack_GetRealChild()
-        Return RealScroll IsNot Nothing AndAlso RealScroll.Visibility = Visibility.Visible AndAlso RealScroll.VerticalOffset > Height + If(BtnExtraBack.Show, 0, 1500)
+        Return RealScroll IsNot Nothing AndAlso RealScroll.Visibility = Visibility.Visible AndAlso RealScroll.VerticalOffset > Height + If(BtnExtraBack.Show, 0, 700)
     End Function
     End Function
     Private Function BtnExtraBack_GetRealChild() As MyScrollViewer
     Private Function BtnExtraBack_GetRealChild() As MyScrollViewer
         If PanMainRight.Child Is Nothing Then Return Nothing
         If PanMainRight.Child Is Nothing Then Return Nothing

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


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

@@ -10,12 +10,12 @@ Public Module ModBase
 #Region "声明"
 #Region "声明"
 
 
     '下列版本信息由更新器自动修改
     '下列版本信息由更新器自动修改
-    Public Const VersionBaseName As String = "2.4.9" '不含分支前缀的显示用版本名
-    Public Const VersionStandardCode As String = "2.4.9." & VersionBranchCode '标准格式的四段式版本号
+    Public Const VersionBaseName As String = "2.5.0" '不含分支前缀的显示用版本名
+    Public Const VersionStandardCode As String = "2.5.0." & VersionBranchCode '标准格式的四段式版本号
 #If BETA Then
 #If BETA Then
     Public Const VersionCode As Integer = 281 'Release
     Public Const VersionCode As Integer = 281 'Release
 #Else
 #Else
-    Public Const VersionCode As Integer = 282 'Snapshot
+    Public Const VersionCode As Integer = 283 'Snapshot
 #End If
 #End If
     '自动生成的版本信息
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -1241,7 +1241,7 @@ Re:
     Public Function ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing) As Boolean
     Public Function ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing) As Boolean
         Try
         Try
             Directory.CreateDirectory(DestDirectory)
             Directory.CreateDirectory(DestDirectory)
-            If CompressFilePath.EndsWith(".zip") OrElse CompressFilePath.EndsWith(".jar") Then
+            If CompressFilePath.EndsWith(".zip") OrElse CompressFilePath.EndsWith(".jar") OrElse CompressFilePath.EndsWith(".mrpack") Then
                 '以 zip 方式解压
                 '以 zip 方式解压
                 ZipFile.ExtractToDirectory(CompressFilePath, DestDirectory, If(Encode, Encoding.GetEncoding("GB18030")))
                 ZipFile.ExtractToDirectory(CompressFilePath, DestDirectory, If(Encode, Encoding.GetEncoding("GB18030")))
                 Return True
                 Return True
@@ -1757,25 +1757,7 @@ Re:
     ''' <summary>
     ''' <summary>
     ''' 数组去重。
     ''' 数组去重。
     ''' </summary>
     ''' </summary>
-    Public Function ArrayNoDouble(Of T)(Arr As T(), Optional IsEqual As CompareThreadStart = Nothing) As T()
-        Dim ResultArray As New List(Of T)
-        For i = 0 To UBound(Arr)
-            For ii = i + 1 To UBound(Arr)
-                If IsEqual Is Nothing Then
-                    If Arr(i).Equals(Arr(ii)) Then GoTo NextElement
-                Else
-                    If IsEqual(Arr(i), Arr(ii)) Then GoTo NextElement
-                End If
-            Next
-            ResultArray.Add(Arr(i))
-NextElement:
-        Next i
-        Return ResultArray.ToArray
-    End Function
-    ''' <summary>
-    ''' 数组去重。
-    ''' </summary>
-    Public Function ArrayNoDouble(Of T)(Arr As List(Of T), Optional IsEqual As CompareThreadStart = Nothing) As List(Of T)
+    Public Function Distinct(Of T)(Arr As ICollection(Of T), Optional IsEqual As CompareThreadStart(Of T) = Nothing) As List(Of T)
         Dim ResultArray As New List(Of T)
         Dim ResultArray As New List(Of T)
         For i = 0 To Arr.Count - 1
         For i = 0 To Arr.Count - 1
             For ii = i + 1 To Arr.Count - 1
             For ii = i + 1 To Arr.Count - 1
@@ -1790,17 +1772,6 @@ NextElement:
         Next i
         Next i
         Return ResultArray
         Return ResultArray
     End Function
     End Function
-    ''' <summary>
-    ''' 若 Key 不存在于辞典,则加入辞典。
-    ''' 若 Key 已存在于辞典,则更新 Value。
-    ''' </summary>
-    Public Sub DictionaryAdd(Of TKey, TValue)(ByRef Dict As Dictionary(Of TKey, TValue), Key As TKey, Value As TValue)
-        If Dict.ContainsKey(Key) Then
-            Dict(Key) = Value
-        Else
-            Dict.Add(Key, Value)
-        End If
-    End Sub
 
 
     ''' <summary>
     ''' <summary>
     ''' 获取格式类似于“11:08:52.037”的当前时间的字符串。
     ''' 获取格式类似于“11:08:52.037”的当前时间的字符串。
@@ -2016,7 +1987,7 @@ NextElement:
     ''' 按照既定的函数进行选择排序。
     ''' 按照既定的函数进行选择排序。
     ''' </summary>
     ''' </summary>
     ''' <param name="SortRule">传入两个对象,若第一个对象应该排在前面,则返回 True。</param>
     ''' <param name="SortRule">传入两个对象,若第一个对象应该排在前面,则返回 True。</param>
-    Public Function Sort(Of T)(List As IList(Of T), SortRule As CompareThreadStart) As List(Of T)
+    Public Function Sort(Of T)(List As IList(Of T), SortRule As CompareThreadStart(Of T)) As List(Of T)
         Dim NewList As New List(Of T)
         Dim NewList As New List(Of T)
         While List.Count > 0
         While List.Count > 0
             Dim Highest = List(0)
             Dim Highest = List(0)
@@ -2028,7 +1999,7 @@ NextElement:
         End While
         End While
         Return NewList
         Return NewList
     End Function
     End Function
-    Public Delegate Function CompareThreadStart(Left, Right) As Boolean
+    Public Delegate Function CompareThreadStart(Of T)(Left As T, Right As T) As Boolean
 
 
     ''' <summary>
     ''' <summary>
     ''' 获取程序启动参数。
     ''' 获取程序启动参数。

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

@@ -679,7 +679,7 @@ Restart:
             Log(ex, "文件夹加载器启动检测出错")
             Log(ex, "文件夹加载器启动检测出错")
         End Try
         End Try
         '写入检查数据
         '写入检查数据
-        DictionaryAdd(LoaderFolderDictionary, Loader, Value)
+        LoaderFolderDictionary(Loader) = Value
         '开始检查
         '开始检查
         If Type = LoaderFolderRunType.UpdateOnly Then Return False
         If Type = LoaderFolderRunType.UpdateOnly Then Return False
         If WaitForExit Then
         If WaitForExit Then

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

@@ -891,7 +891,7 @@ RequestFinished:
         Public Sub New(Urls As String(), LocalPath As String, Optional Check As FileChecker = Nothing)
         Public Sub New(Urls As String(), LocalPath As String, Optional Check As FileChecker = Nothing)
             Dim Sources As New List(Of NetSource)
             Dim Sources As New List(Of NetSource)
             Dim Count As Integer = 0
             Dim Count As Integer = 0
-            Urls = ArrayNoDouble(Urls)
+            Urls = Urls.Distinct.ToArray
             For Each Source As String In Urls
             For Each Source As String In Urls
                 Sources.Add(New NetSource With {.FailCount = 0, .Url = SecretCdnSign(Source.Replace(vbCr, "").Replace(vbLf, "").Trim), .Id = Count, .IsFailed = False, .Ex = Nothing})
                 Sources.Add(New NetSource With {.FailCount = 0, .Url = SecretCdnSign(Source.Replace(vbCr, "").Replace(vbLf, "").Trim), .Id = Count, .IsFailed = False, .Ex = Nothing})
                 Count += 1
                 Count += 1
@@ -1585,7 +1585,7 @@ NextElement:
                                        For Each Folder In McFolderList
                                        For Each Folder In McFolderList
                                            Folders.Add(Folder.Path)
                                            Folders.Add(Folder.Path)
                                        Next
                                        Next
-                                       Folders = ArrayNoDouble(Folders)
+                                       Folders = Folders.Distinct.ToList
                                        For Each Folder In Folders
                                        For Each Folder In Folders
                                            If Folder <> PathMcFolder AndAlso Directory.Exists(Folder) Then FoldersFinal.Add(Folder)
                                            If Folder <> PathMcFolder AndAlso Directory.Exists(Folder) Then FoldersFinal.Add(Folder)
                                        Next
                                        Next
@@ -1725,7 +1725,7 @@ Retry:
             For Each Ex As Exception In ExList
             For Each Ex As Exception In ExList
                 ErrOutput.Add(GetExceptionDetail(Ex))
                 ErrOutput.Add(GetExceptionDetail(Ex))
             Next
             Next
-            Log("[Download] " & Join(ArrayNoDouble(ErrOutput.ToArray), vbCrLf))
+            Log("[Download] " & Join(ErrOutput.Distinct.ToArray, vbCrLf))
         End Sub
         End Sub
         Public Overrides Sub Abort()
         Public Overrides Sub Abort()
             SyncLock LockState
             SyncLock LockState

+ 1216 - 0
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb

@@ -0,0 +1,1216 @@
+Public Module ModComp
+
+    Public Enum CompType
+        ''' <summary>
+        ''' Mod。
+        ''' </summary>
+        [Mod]
+        ''' <summary>
+        ''' 整合包。
+        ''' </summary>
+        ModPack
+        ''' <summary>
+        ''' 资源包。
+        ''' </summary>
+        ResourcePack
+    End Enum
+    Public Enum CompModLoaderType
+        'https://docs.curseforge.com/?http#tocS_ModLoaderType
+        Any = 0
+        Forge = 1
+        Fabric = 4
+        Quilt = 5
+    End Enum
+
+#Region "CompDatabase | Mod 数据库"
+
+    Private _CompDatabase As List(Of CompDatabaseEntry) = Nothing
+    Private ReadOnly Property CompDatabase As List(Of CompDatabaseEntry)
+        Get
+            If _CompDatabase IsNot Nothing Then Return _CompDatabase
+            '初始化数据库
+            _CompDatabase = New List(Of CompDatabaseEntry)
+            Dim i As Integer = 0
+            For Each Line In DecodeBytes(GetResources("ModData")).Replace(vbCrLf, vbLf).Replace(vbCr, "").Split(vbLf)
+                i += 1
+                If Line = "" Then Continue For
+                For Each EntryData As String In Line.Split("¨")
+                    Dim Entry = New CompDatabaseEntry
+                    Dim SplitedLine = EntryData.Split("|")
+                    If SplitedLine(0).StartsWith("@") Then
+                        Entry.CurseForgeSlug = Nothing
+                        Entry.ModrinthSlug = SplitedLine(0).Replace("@", "")
+                    ElseIf SplitedLine(0).EndsWith("@") Then
+                        Entry.CurseForgeSlug = SplitedLine(0).TrimEnd("@")
+                        Entry.ModrinthSlug = Entry.CurseForgeSlug
+                    ElseIf SplitedLine(0).Contains("@") Then
+                        Entry.CurseForgeSlug = SplitedLine(0).Split("@")(0)
+                        Entry.ModrinthSlug = SplitedLine(0).Split("@")(1)
+                    Else
+                        Entry.CurseForgeSlug = SplitedLine(0)
+                        Entry.ModrinthSlug = Nothing
+                    End If
+                    Entry.WikiId = i
+                    If SplitedLine.Count >= 2 Then
+                        Entry.ChineseName = SplitedLine(1)
+                        Entry.MCBBS = If(SplitedLine.Count >= 3, SplitedLine(2), Nothing)
+                        If Entry.ChineseName.Contains("*") Then '处理 *
+                            Entry.ChineseName = Entry.ChineseName.Replace("*", " (" &
+                                String.Join(" ", If(Entry.CurseForgeSlug, Entry.ModrinthSlug).Split("-").Select(Function(w) w.Substring(0, 1).ToUpper & w.Substring(1, w.Length - 1))) & ")")
+                        End If
+                    End If
+                    _CompDatabase.Add(Entry)
+                Next
+            Next
+            Return _CompDatabase
+        End Get
+    End Property
+
+    Private Class CompDatabaseEntry
+        ''' <summary>
+        ''' McMod 的对应 ID。
+        ''' </summary>
+        Public WikiId As Integer
+        ''' <summary>
+        ''' 中文译名。空字符串代表没有翻译。
+        ''' </summary>
+        Public ChineseName As String = ""
+        ''' <summary>
+        ''' MCBBS 发布帖编号(例如 195107)。若为 Nothing 则没有发布帖。
+        ''' </summary>
+        Public MCBBS As Integer?
+        ''' <summary>
+        ''' CurseForge Slug(例如 advanced-solar-panels)。
+        ''' </summary>
+        Public CurseForgeSlug As String = Nothing
+        ''' <summary>
+        ''' Modrinth Slug(例如 advanced-solar-panels)。
+        ''' </summary>
+        Public ModrinthSlug As String = Nothing
+
+        Public Overrides Function ToString() As String
+            Return If(CurseForgeSlug, "") & "&" & If(ModrinthSlug, "") & "|" & WikiId & "|" & ChineseName & If(MCBBS Is Nothing, "", "|" & MCBBS)
+        End Function
+    End Class
+
+#End Region
+
+#Region "CompProject | 工程信息"
+
+    '类定义
+
+    Public Class CompProject
+
+        '源信息
+
+        ''' <summary>
+        ''' 该工程信息来自 CurseForge 还是 Modrinth。
+        ''' </summary>
+        Public ReadOnly FromCurseForge As Boolean
+        ''' <summary>
+        ''' 工程的种类。
+        ''' </summary>
+        Public ReadOnly Type As CompType
+        ''' <summary>
+        ''' 工程的短名。例如 technical-enchant。
+        ''' </summary>
+        Public ReadOnly Slug As String
+        ''' <summary>
+        ''' CurseForge 工程的数字 ID。Modrinth 工程的乱码 ID。
+        ''' </summary>
+        Public ReadOnly Id As String
+        ''' <summary>
+        ''' CurseForge 文件列表的数字 ID。Modrinth 工程的此项无效。
+        ''' </summary>
+        Public ReadOnly CurseForgeFileIds As List(Of Integer)
+
+        '描述性信息
+
+        ''' <summary>
+        ''' 原始的英文名称。
+        ''' </summary>
+        Public ReadOnly RawName As String
+        ''' <summary>
+        ''' 英文描述。
+        ''' </summary>
+        Public ReadOnly Description As String
+        ''' <summary>
+        ''' 来源网站的工程页面网址。确保格式一定标准。
+        ''' CurseForge:https://www.curseforge.com/minecraft/mc-mods/jei
+        ''' Modrinth:https://modrinth.com/mod/technical-enchant
+        ''' </summary>
+        Public ReadOnly Website As String
+        ''' <summary>
+        ''' 最后一次更新的时间。可能为 Nothing。
+        ''' </summary>
+        Public ReadOnly LastUpdate As Date? = Nothing
+        ''' <summary>
+        ''' 下载量计数。注意,该计数仅为一个来源,无法反应两边加起来的下载量!
+        ''' </summary>
+        Public ReadOnly DownloadCount As Integer
+        ''' <summary>
+        ''' 支持的 Mod 加载器列表。可能为空。
+        ''' </summary>
+        Public ReadOnly ModLoaders As List(Of CompModLoaderType)
+        ''' <summary>
+        ''' 描述性标签的内容。已转换为中文。
+        ''' </summary>
+        Public ReadOnly Tags As List(Of String)
+        ''' <summary>
+        ''' Logo 图片的下载地址。若为 Nothing 则没有。
+        ''' </summary>
+        Public ReadOnly LogoUrl As String = Nothing
+        ''' <summary>
+        ''' 游戏大版本列表。例如:18, 16, 15……
+        ''' </summary>
+        Public ReadOnly GameVersions As List(Of Integer)
+
+        '数据库信息
+
+        Private LoadedDatabase As Boolean = False
+        Private _DatabaseEntry As CompDatabaseEntry = Nothing
+        ''' <summary>
+        ''' 关联的数据库条目。若为 Nothing 则没有。
+        ''' </summary>
+        Private Property DatabaseEntry As CompDatabaseEntry
+            Get
+                If Not LoadedDatabase Then
+                    LoadedDatabase = True
+                    _DatabaseEntry = CompDatabase.FirstOrDefault(Function(c) If(FromCurseForge, c.CurseForgeSlug, c.ModrinthSlug) = Slug)
+                End If
+                Return _DatabaseEntry
+            End Get
+            Set(value As CompDatabaseEntry)
+                LoadedDatabase = True
+                _DatabaseEntry = value
+            End Set
+        End Property
+        ''' <summary>
+        ''' MC 百科的页面 ID。若为 0 则没有。
+        ''' </summary>
+        Public ReadOnly Property WikiId As Integer
+            Get
+                Return If(DatabaseEntry Is Nothing, 0, DatabaseEntry.WikiId)
+            End Get
+        End Property
+        ''' <summary>
+        ''' MCBBS 的帖子 ID。若为 0 则没有。
+        ''' </summary>
+        Public ReadOnly Property MCBBS As Integer
+            Get
+                Return If(DatabaseEntry Is Nothing, 0, If(DatabaseEntry.MCBBS, 0))
+            End Get
+        End Property
+        ''' <summary>
+        ''' 翻译后的中文名。若数据库没有则等同于 RawName。
+        ''' </summary>
+        Public ReadOnly Property TranslatedName As String
+            Get
+                Return If(DatabaseEntry Is Nothing OrElse DatabaseEntry.ChineseName = "", RawName, DatabaseEntry.ChineseName)
+            End Get
+        End Property
+
+        '实例化
+
+        ''' <summary>
+        ''' 从工程 Json 中初始化实例。若出错会抛出异常。
+        ''' </summary>
+        Public Sub New(Data As JObject)
+            FromCurseForge = Data.ContainsKey("gameId")
+            If FromCurseForge Then
+#Region "CurseForge"
+                '简单信息
+                Id = Data("id")
+                Slug = Data("slug")
+                RawName = Data("name")
+                Description = Data("summary")
+                Website = Data("links")("websiteUrl").ToString.TrimEnd("/")
+                LastUpdate = Data("dateModified")
+                DownloadCount = Data("downloadCount")
+                'Logo
+                If Data("logo").Count > 0 Then
+                    If GetPixelSize(1) > 1.25 Then
+                        LogoUrl = Data("logo")("thumbnailUrl").ToString '使用 256x256 图标
+                    Else
+                        LogoUrl = Data("logo")("thumbnailUrl").ToString.Replace("/256/256/", "/64/64/") '使用 64x64 图标
+                    End If
+                End If
+                'FileIndexes / GameVersions
+                Dim Files = If(CType(Data("latestFilesIndexes"), JArray), New JArray).
+                            Where(Function(i) i("gameVersion").ToString.Contains("1."))
+                CurseForgeFileIds = Files.Select(Of Integer)(Function(v) v("fileId")).ToList
+                GameVersions = Files.Select(Of Integer)(Function(v) Val(v("gameVersion").ToString.Split(".")(1).Split("-").First)).Where(Function(v) v > 0).
+                               Distinct.OrderByDescending(Function(v) v).ToList
+                'Type
+                If Website.Contains("/mc-mods/") OrElse Website.Contains("/mod/") Then
+                    Type = CompType.Mod
+                ElseIf Website.Contains("/modpacks/") Then
+                    Type = CompType.ModPack
+                Else
+                    Type = CompType.ResourcePack
+                End If
+                'Tags
+                Tags = New List(Of String)
+                For Each Category In Data("categories").Select(Of Integer)(Function(t) t("id")).Distinct.OrderByDescending(Function(c) c)
+                    Select Case Category
+                        'Mod
+                        Case 406 : Tags.Add("世界元素")
+                        Case 407 : Tags.Add("生物群系")
+                        Case 410 : Tags.Add("维度")
+                        Case 408 : Tags.Add("矿物和资源")
+                        Case 409 : Tags.Add("天然结构")
+                        Case 412 : Tags.Add("科技")
+                        Case 415 : Tags.Add("管道与物流")
+                        Case 4843 : Tags.Add("自动化")
+                        Case 417 : Tags.Add("能源")
+                        Case 4558 : Tags.Add("红石")
+                        Case 436 : Tags.Add("食物与烹饪")
+                        Case 416 : Tags.Add("农业")
+                        Case 414 : Tags.Add("运输")
+                        Case 420 : Tags.Add("仓储")
+                        Case 419 : Tags.Add("魔法")
+                        Case 422 : Tags.Add("冒险")
+                        Case 424 : Tags.Add("装饰")
+                        Case 411 : Tags.Add("生物")
+                        Case 434 : Tags.Add("装备")
+                        Case 423 : Tags.Add("信息显示")
+                        Case 435 : Tags.Add("多人")
+                        Case 5191 : Tags.Add("改良")
+                        Case 421 : Tags.Add("支持库")
+                        '整合包
+                        Case 4484 : Tags.Add("多人")
+                        Case 4479 : Tags.Add("硬核")
+                        Case 4483 : Tags.Add("战斗")
+                        Case 4478 : Tags.Add("任务")
+                        Case 4472 : Tags.Add("科技")
+                        Case 4473 : Tags.Add("魔法")
+                        Case 4475 : Tags.Add("冒险")
+                        Case 4476 : Tags.Add("探索")
+                        Case 4477 : Tags.Add("小游戏")
+                        Case 4471 : Tags.Add("科幻")
+                        Case 4736 : Tags.Add("空岛")
+                        Case 5128 : Tags.Add("原版改良")
+                        Case 4487 : Tags.Add("FTB")
+                        Case 4480 : Tags.Add("基于地图")
+                        Case 4481 : Tags.Add("轻量整合")
+                        Case 4482 : Tags.Add("大型整合")
+                            'FUTURE: Res
+                    End Select
+                Next
+                If Tags.Count = 0 Then Tags.Add("杂项")
+                'ModLoaders
+                ModLoaders = New List(Of CompModLoaderType)
+                For Each File In If(Data("latestFiles"), {})
+                    Dim NewFile As New CompFile(File, Type)
+                    ModLoaders.AddRange(NewFile.ModLoaders)
+                Next
+                ModLoaders = ModLoaders.Distinct.OrderBy(Of Integer)(Function(t) t).ToList
+#End Region
+            Else
+#Region "Modrinth"
+                '简单信息
+                Id = If(Data("project_id"), Data("id")) '两个 API 会返回的 key 不一样
+                Slug = Data("slug")
+                RawName = Data("title")
+                Description = Data("description")
+                LastUpdate = Data("date_modified")
+                DownloadCount = Data("downloads")
+                LogoUrl = Data("icon_url")
+                If LogoUrl = "" Then LogoUrl = Nothing
+                Website = $"https://modrinth.com/{Data("project_type")}/{Slug}"
+                'GameVersions
+                GameVersions = If(CType(Data("versions"), JArray), New JArray).
+                                   Select(Function(v) v.ToString).Where(Function(v) v.Contains("1.")).
+                                   Select(Of Integer)(Function(v) Val(v.Split(".")(1).Split("-").First)).Where(Function(v) v > 0).
+                                   Distinct.OrderByDescending(Function(v) v).ToList
+                'Type
+                Select Case Data("project_type").ToString
+                    Case "mod" : Type = CompType.Mod
+                    Case "modpack" : Type = CompType.ModPack
+                    Case "resourcepack" : Type = CompType.ResourcePack
+                End Select
+                'Tags & ModLoaders
+                Tags = New List(Of String)
+                ModLoaders = New List(Of CompModLoaderType)
+                For Each Category In Data("categories").Select(Function(t) t.ToString)
+                    Select Case Category
+                        '加载器
+                        Case "forge" : ModLoaders.Add(CompModLoaderType.Forge)
+                        Case "fabric" : ModLoaders.Add(CompModLoaderType.Fabric)
+                        Case "quilt" : ModLoaders.Add(CompModLoaderType.Quilt)
+                        'Mod
+                        Case "worldgen" : Tags.Add("世界元素")
+                        Case "technology" : Tags.Add("科技")
+                        Case "food" : Tags.Add("食物与烹饪")
+                        Case "game-mechanics" : Tags.Add("游戏机制")
+                        Case "transportation" : Tags.Add("运输")
+                        Case "storage" : Tags.Add("仓储")
+                        Case "magic" : Tags.Add("魔法")
+                        Case "adventure" : Tags.Add("冒险")
+                        Case "decoration" : Tags.Add("装饰")
+                        Case "mobs" : Tags.Add("生物")
+                        Case "equipment" : Tags.Add("装备")
+                        Case "optimization" : Tags.Add("性能优化")
+                        Case "social" : Tags.Add("多人")
+                        Case "utility" : Tags.Add("改良")
+                        Case "library" : Tags.Add("支持库")
+                        '整合包
+                        Case "multiplayer" : Tags.Add("多人")
+                        Case "optimization" : Tags.Add("性能优化")
+                        Case "challenging" : Tags.Add("硬核")
+                        Case "combat" : Tags.Add("战斗")
+                        Case "quests" : Tags.Add("任务")
+                        Case "technology" : Tags.Add("科技")
+                        Case "magic" : Tags.Add("魔法")
+                        Case "adventure" : Tags.Add("冒险")
+                        Case "kitchen-sink" : Tags.Add("烹饪")
+                        Case "lightweight" : Tags.Add("轻量整合")
+                            'FUTURE: Res
+                    End Select
+                Next
+                If Tags.Count = 0 Then Tags.Add("杂项")
+                ModLoaders.Sort()
+#End Region
+            End If
+            '保存缓存
+            CompProjectCache(Id) = Me
+        End Sub
+        ''' <summary>
+        ''' 将当前工程信息实例化为控件。
+        ''' </summary>
+        Public Function ToCompItem(ShowMcVersionDesc As Boolean, ShowLoaderDesc As Boolean, Optional OnClick As MyCompItem.ClickEventHandler = Nothing) As MyCompItem
+            '获取版本描述
+            Dim GameVersionDescription As String
+            If GameVersions Is Nothing OrElse GameVersions.Count = 0 Then
+                GameVersionDescription = ""
+            Else
+                Dim SpaVersions As New List(Of String)
+                For i = 0 To GameVersions.Count - 1
+                    Dim StartVersion As Integer = GameVersions(i), EndVersion As Integer = GameVersions(i)
+                    For ii = i + 1 To GameVersions.Count - 1
+                        If GameVersions(ii) = EndVersion - 1 Then
+                            EndVersion = GameVersions(ii)
+                            i = ii
+                        Else
+                            Exit For
+                        End If
+                    Next
+                    If StartVersion = EndVersion Then
+                        SpaVersions.Add("1." & StartVersion)
+                    ElseIf McVersionHighest > -1 AndAlso StartVersion >= McVersionHighest Then
+                        SpaVersions.Add("1." & EndVersion & "+")
+                    ElseIf EndVersion <= 7 Then
+                        SpaVersions.Add("1." & StartVersion & "-")
+                        Exit For
+                    ElseIf StartVersion - EndVersion = 1 Then
+                        SpaVersions.Add("1." & StartVersion & ", 1." & EndVersion)
+                    Else
+                        SpaVersions.Add("1." & StartVersion & "~1." & EndVersion)
+                    End If
+                Next
+                GameVersionDescription = "[" & Join(SpaVersions, ", ") & "] "
+            End If
+            '实例化 UI
+            Dim NewItem As New MyCompItem With {.Tag = Me}
+            NewItem.LabTitle.Text = TranslatedName
+            NewItem.LabInfo.Text = Description.Replace(vbCr, "").Replace(vbLf, "")
+            NewItem.LabLeft.Text = If(ModeDebug, If(FromCurseForge, "[CurseForge] ", "[Modrinth] "), "") &
+                                   (If(ModLoaders.Count > 0 AndAlso ShowLoaderDesc, "[" & Join(ModLoaders.Select(Function(m) GetStringFromEnum(m)).ToList, "/") & "] ", "") &
+                                   If(ShowMcVersionDesc, GameVersionDescription, "")).Replace("] [", " ") &
+                                   Join(Tags, ",") &
+                                   If(LastUpdate IsNot Nothing, " (" & GetTimeSpanString(LastUpdate - Date.Now) & "更新)", "")
+            If LogoUrl Is Nothing Then
+                NewItem.Logo = "pack://application:,,,/images/Icons/NoIcon.png"
+            Else
+                NewItem.Logo = LogoUrl
+            End If
+            If OnClick IsNot Nothing Then
+                AddHandler NewItem.Click, OnClick
+                NewItem.HasAnimation = True
+            Else
+                NewItem.HasAnimation = False
+            End If
+            Return NewItem
+        End Function
+
+        '辅助函数
+
+        ''' <summary>
+        ''' 检查是否与某个 Project 是相同的工程,只是在不同的网站。
+        ''' </summary>
+        Public Function IsLike(Project As CompProject) As Boolean
+            '提取字符串中的字母和数字
+            Dim GetRaw =
+                Function(Data As String) As String
+                    Dim Result As New StringBuilder()
+                    For Each r As Char In Data.Where(Function(c) Char.IsLetterOrDigit(c))
+                        Result.Append(r)
+                    Next
+                    Return Result.ToString.ToLower
+                End Function
+            '具体的检查
+            '精准匹配:相同实例 或 MCMOD 指定的相同内容
+            '模糊匹配:支持的 Mod 加载器一致,且 (原名、描述文本或 Slug 的英文部分相同)
+            If Id = Project.Id OrElse TranslatedName = Project.TranslatedName OrElse '精准匹配
+               ((RawName = Project.RawName OrElse Description = Project.Description OrElse GetRaw(Slug) = GetRaw(Project.Slug)) AndAlso
+               ModLoaders.Count = Project.ModLoaders.Count AndAlso ModLoaders.Except(Project.ModLoaders).Count = 0) Then '模糊匹配
+                '如果只有一个有 DatabaseEntry,设置给另外一个
+                If DatabaseEntry Is Nothing AndAlso Project.DatabaseEntry IsNot Nothing Then DatabaseEntry = Project.DatabaseEntry
+                If DatabaseEntry IsNot Nothing AndAlso Project.DatabaseEntry Is Nothing Then Project.DatabaseEntry = DatabaseEntry
+                Return True
+            End If
+            Return False
+        End Function
+
+        Public Overrides Function ToString() As String
+            Return $"{Id} ({Slug}): {RawName}"
+        End Function
+        Public Overrides Function Equals(obj As Object) As Boolean
+            Dim project = TryCast(obj, CompProject)
+            Return project IsNot Nothing AndAlso Id = project.Id
+        End Function
+        Public Shared Operator =(left As CompProject, right As CompProject) As Boolean
+            Return EqualityComparer(Of CompProject).Default.Equals(left, right)
+        End Operator
+        Public Shared Operator <>(left As CompProject, right As CompProject) As Boolean
+            Return Not left = right
+        End Operator
+
+    End Class
+
+    '输入与输出
+
+    Public Class CompProjectRequest
+
+        '结果要求
+
+        ''' <summary>
+        ''' 加载后应输出到的结果存储器。
+        ''' </summary>
+        Public Storage As CompProjectStorage
+        ''' <summary>
+        ''' 应当尽量达成的结果数量。
+        ''' </summary>
+        Public TargetResultCount As Integer
+        ''' <summary>
+        ''' 根据加载位置记录,是否还可以继续获取内容。
+        ''' </summary>
+        Public ReadOnly Property CanContinue As Boolean
+            Get
+                If Tag.StartsWith("/") Then Storage.CurseForgeTotal = 0
+                If Tag.EndsWith("/") Then Storage.ModrinthTotal = 0
+                If Storage.CurseForgeTotal = -1 OrElse Storage.ModrinthTotal = -1 Then Return True
+                Return Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal
+            End Get
+        End Property
+
+        '输入内容
+
+        ''' <summary>
+        ''' 筛选资源种类。
+        ''' </summary>
+        Public Type As CompType
+        ''' <summary>
+        ''' 筛选资源标签。空字符串代表不限制。格式例如 "406/worldgen",分别是 CurseForge 和 Modrinth 的 ID。
+        ''' </summary>
+        Public Tag As String = ""
+        ''' <summary>
+        ''' 筛选 Mod 加载器类别。
+        ''' </summary>
+        Public ModLoader As CompModLoaderType = CompModLoaderType.Any
+        ''' <summary>
+        ''' 筛选 MC 版本。
+        ''' </summary>
+        Public GameVersion As String = Nothing
+        ''' <summary>
+        ''' 搜索的文本内容。
+        ''' </summary>
+        Public SearchText As String = Nothing
+        ''' <summary>
+        ''' 构造函数。
+        ''' </summary>
+        Public Sub New(Type As CompType, Storage As CompProjectStorage, TargetResultCount As Integer)
+            Me.Type = Type
+            Me.Storage = Storage
+            Me.TargetResultCount = TargetResultCount
+        End Sub
+
+        '构造请求
+
+        ''' <summary>
+        ''' 获取对应的 CurseForge API 请求链接。若返回 Nothing 则为不进行 CurseForge 请求。
+        ''' </summary>
+        Public Function GetCurseForgeAddress() As String
+            If Tag.StartsWith("/") Then Storage.CurseForgeTotal = 0
+            If Storage.CurseForgeTotal > -1 AndAlso Storage.CurseForgeTotal <= Storage.CurseForgeOffset Then Return Nothing
+            '应用筛选参数
+            Dim Address As String = $"https://api.curseforge.com/v1/mods/search?gameId=432&sortField=Featured&sortOrder=desc&pageSize={CompPageSize}"
+            Select Case Type
+                Case CompType.Mod
+                    Address += "&classId=6"
+                Case CompType.ModPack
+                    Address += "&classId=4471"
+                Case CompType.ResourcePack
+                    'FUTURE: Res
+            End Select
+            Address += "&categoryId=" & If(Tag = "", "0", Tag.Split("/").First)
+            If ModLoader <> CompModLoaderType.Any Then Address += "&modLoaderType=" & CType(ModLoader, Integer)
+            If Not String.IsNullOrEmpty(GameVersion) Then Address += "&gameVersion=" & GameVersion
+            If Not String.IsNullOrEmpty(SearchText) Then Address += "&searchFilter=" & Net.WebUtility.UrlEncode(SearchText)
+            If Storage.CurseForgeOffset > 0 Then Address += "&index=" & Storage.CurseForgeOffset
+            Return Address
+        End Function
+        ''' <summary>
+        ''' 获取对应的 Modrinth API 请求链接。若返回 Nothing 则为不进行 Modrinth 请求。
+        ''' </summary>
+        Public Function GetModrinthAddress() As String
+            If Tag.EndsWith("/") Then Storage.ModrinthTotal = 0
+            If Storage.ModrinthTotal > -1 AndAlso Storage.ModrinthTotal <= Storage.ModrinthOffset Then Return Nothing
+            '应用筛选参数
+            Dim Address As String = $"https://api.modrinth.com/v2/search?limit={CompPageSize}&index=relevance"
+            If Not String.IsNullOrEmpty(SearchText) Then Address += "&query=" & Net.WebUtility.UrlEncode(SearchText)
+            If Storage.ModrinthOffset > 0 Then Address += "&offset=" & Storage.ModrinthOffset
+            'facets=[["categories:'game-mechanics'"],["categories:'forge'"],["versions:1.19.3"],["project_type:mod"]]
+            Dim Facets As New List(Of String)
+            Facets.Add($"[""project_type:{GetStringFromEnum(Type).ToLower}""]")
+            If Not String.IsNullOrEmpty(Tag) Then Facets.Add($"[""categories:'{Tag.Split("/").Last}'""]")
+            If ModLoader <> CompModLoaderType.Any Then Facets.Add($"[""categories:'{GetStringFromEnum(ModLoader).ToLower}'""]")
+            If Not String.IsNullOrEmpty(GameVersion) Then Facets.Add($"[""versions:'{GameVersion}'""]")
+            Address += "&facets=[" & String.Join(",", Facets) & "]"
+            Return Address
+        End Function
+
+        '相同判断
+        Public Overrides Function Equals(obj As Object) As Boolean
+            Dim request = TryCast(obj, CompProjectRequest)
+            Return request IsNot Nothing AndAlso
+                Type = request.Type AndAlso TargetResultCount = request.TargetResultCount AndAlso
+                Tag = request.Tag AndAlso ModLoader = request.ModLoader AndAlso
+                GameVersion = request.GameVersion AndAlso SearchText = request.SearchText
+        End Function
+        Public Shared Operator =(left As CompProjectRequest, right As CompProjectRequest) As Boolean
+            Return EqualityComparer(Of CompProjectRequest).Default.Equals(left, right)
+        End Operator
+        Public Shared Operator <>(left As CompProjectRequest, right As CompProjectRequest) As Boolean
+            Return Not left = right
+        End Operator
+
+    End Class
+    Public Class CompProjectStorage
+
+        '加载位置记录
+
+        Public CurseForgeOffset As Integer = 0
+        Public CurseForgeTotal As Integer = -1
+
+        Public ModrinthOffset As Integer = 0
+        Public ModrinthTotal As Integer = -1
+
+        '结果列表
+
+        ''' <summary>
+        ''' 可供展示的所有工程的列表。
+        ''' </summary>
+        Public Results As New List(Of CompProject)
+
+    End Class
+
+    '实际的获取
+
+    Private Const CompPageSize = 40
+    ''' <summary>
+    ''' 已知工程信息的缓存。
+    ''' </summary>
+    Private CompProjectCache As New Dictionary(Of String, CompProject)
+    ''' <summary>
+    ''' 根据搜索请求获取一系列的工程列表。需要基于加载器运行。
+    ''' </summary>
+    Public Sub CompProjectsGet(Task As LoaderTask(Of CompProjectRequest, Integer))
+
+        If Task.Input.Storage.Results.Count >= Task.Input.TargetResultCount Then
+            Log($"[Comp] 已有 {Task.Input.Storage.Results.Count} 个结果,多于所需的 {Task.Input.TargetResultCount} 个结果,结束处理")
+            Exit Sub
+        ElseIf Not Task.Input.CanContinue Then
+            Log($"[Comp] 已有 {Task.Input.Storage.Results.Count} 个结果,少于所需的 {Task.Input.TargetResultCount} 个结果,但无法继续获取,结束处理")
+            Exit Sub
+        End If
+
+#Region "拒绝 1.13- Quilt(这个版本根本没有 Quilt)"
+
+        If Task.Input.ModLoader = CompModLoaderType.Quilt AndAlso VersionSortInteger(If(Task.Input.GameVersion, "1.15"), "1.14") = -1 Then
+            Throw New Exception("Quilt 不支持 Minecraft " & Task.Input.GameVersion)
+        End If
+
+#End Region
+
+#Region "处理搜索文本,赋值回 Task.Input.SearchText"
+
+        Dim RawFilter As String = If(Task.Input.SearchText, "").Trim
+        Task.Input.SearchText = RawFilter
+        RawFilter = RawFilter.ToLower
+        Log("[Comp] 工程列表搜索原始文本:" & RawFilter)
+
+        '中文请求关键字处理
+        Dim IsChineseSearch As Boolean = RegexCheck(RawFilter, "[\u4e00-\u9fbb]")
+        If IsChineseSearch AndAlso Not String.IsNullOrEmpty(RawFilter) Then
+            If Task.Input.Type <> CompType.Mod Then Throw New Exception($"{If(Task.Input.Type = CompType.ModPack, "整合包", "资源包")}搜索仅支持英文")
+            '构造搜索请求
+            Dim SearchEntries As New List(Of SearchEntry(Of CompDatabaseEntry))
+            For Each Entry In CompDatabase
+                If Entry.ChineseName.Contains("动态的树") Then Continue For '这玩意儿附属太多了
+                SearchEntries.Add(New SearchEntry(Of CompDatabaseEntry) With {
+                    .Item = Entry,
+                    .SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
+                        New KeyValuePair(Of String, Double)(Entry.ChineseName & If(Entry.CurseForgeSlug, "") & If(Entry.ModrinthSlug, ""), 1)
+                    }
+                })
+            Next
+            '获取搜索结果
+            Dim SearchResults = Search(SearchEntries, Task.Input.SearchText, 3)
+            If SearchResults.Count = 0 Then Throw New Exception("无搜索结果,请尝试搜索英文名称")
+            Dim SearchResult As String = ""
+            For i = 0 To Math.Min(4, SearchResults.Count - 1) '就算全是准确的,也最多只要 5 个
+                If Not SearchResults(i).AbsoluteRight AndAlso i >= Math.Min(2, SearchResults.Count - 1) Then Exit For '把 3 个结果拼合以提高准确度
+                If SearchResults(i).Item.CurseForgeSlug IsNot Nothing Then SearchResult += SearchResults(i).Item.CurseForgeSlug.Replace("-", " ") & " "
+                If SearchResults(i).Item.ModrinthSlug IsNot Nothing Then SearchResult += SearchResults(i).Item.ModrinthSlug.Replace("-", " ") & " "
+                SearchResult += SearchResults(i).Item.ChineseName.Replace(" (", "|").Split("|").Last.TrimEnd(") ").Replace(" - ", "§").Split("§").First.
+                    Replace(":", "").Replace("(", "").Replace(")", "").ToLower & " "
+            Next
+            Log("[Comp] 中文搜索原始关键词:" & SearchResult, LogLevel.Developer)
+            '去除常见连接词
+            Dim RealFilter As String = ""
+            For Each Word In SearchResult.Split(" ")
+                If {"the", "of", "a", "mod"}.Contains(Word.ToLower) OrElse Val(Word) > 0 Then Continue For
+                If SearchResult.Split(" ").Count > 3 AndAlso {"ftb"}.Contains(Word.ToLower) Then Continue For
+                RealFilter += Word.TrimStart("{[(").TrimEnd("}])") & " "
+            Next
+            Task.Input.SearchText = RealFilter
+            Log("[Comp] 中文搜索最终关键词:" & RealFilter, LogLevel.Developer)
+        End If
+
+        '驼峰英文请求关键字处理
+        Dim SpacedKeywords = RegexReplace(Task.Input.SearchText, "$& ", "([A-Z]+|[a-z]+?)(?=[A-Z]+[a-z]+[a-z ]*)")
+        Dim ConnectedKeywords = Task.Input.SearchText.Replace(" ", "")
+        Dim AllPossibleKeywords = (SpacedKeywords & " " & If(IsChineseSearch, Task.Input.SearchText, ConnectedKeywords & " " & RawFilter)).ToLower
+
+        '最终处理关键字:分割、去重
+        Dim RightKeywords As New List(Of String)
+        For Each Keyword In AllPossibleKeywords.Split(" ")
+            If Keyword.Trim = "" Then Continue For
+            If {"forge", "fabric", "for", "mod",
+                "forge/fabric", "fabric/forge", "fabric/quilt", "quilt/fabric"}.Contains(Keyword) Then '#208
+                Log("[Comp] 已跳过搜索关键词:" & Keyword)
+                Continue For
+            End If
+            RightKeywords.Add(Keyword)
+        Next
+        If RawFilter.Length > 0 AndAlso RightKeywords.Count = 0 Then
+            Task.Input.SearchText = RawFilter '全都被过滤掉了
+        Else
+            Task.Input.SearchText = Join(RightKeywords.Distinct.ToList, " ").ToLower
+        End If
+
+        '例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod)
+        If RawFilter.Replace(" ", "").ToLower.Contains("optiforge") Then Task.Input.SearchText = "optiforge"
+        If RawFilter.Replace(" ", "").ToLower.Contains("optifabric") Then Task.Input.SearchText = "optifabric"
+        Log("[Comp] 工程列表搜索最终文本:" & Task.Input.SearchText, LogLevel.Developer)
+        Task.Progress = 0.1
+
+#End Region
+
+        Dim RealResults As New List(Of CompProject)
+Retry:
+        Dim RawResults As New List(Of CompProject)
+
+#Region "从 CurseForge 和 Modrinth 获取结果列表,存储于 RawResults"
+
+        Dim CurseForgeThread As Thread = Nothing
+        Dim ModrinthThread As Thread = Nothing
+        Dim ResultsLock As New Object
+        Dim [Error] As Exception = Nothing
+
+        Try
+
+            '启动 CurseForge 线程
+            Dim CurseForgeUrl As String = Task.Input.GetCurseForgeAddress()
+            If CurseForgeUrl IsNot Nothing Then
+                CurseForgeThread = RunInNewThread(
+                    Sub()
+                        Try
+                            '获取工程列表
+                            Log("[Comp] 开始从 CurseForge 获取工程列表:" & CurseForgeUrl)
+                            Dim RequestResult As JObject = NetGetCodeByRequestRetry(CurseForgeUrl, IsJson:=True, Encode:=Encoding.UTF8)
+                            Task.Progress += 0.2
+                            Dim ProjectList As New List(Of CompProject)
+                            For Each JsonEntry As JObject In RequestResult("data")
+                                ProjectList.Add(New CompProject(JsonEntry))
+                            Next
+                            '更新结果
+                            SyncLock ResultsLock
+                                RawResults.AddRange(ProjectList)
+                            End SyncLock
+                            Task.Input.Storage.CurseForgeOffset += ProjectList.Count
+                            Task.Input.Storage.CurseForgeTotal = RequestResult("pagination")("totalCount").ToObject(Of Integer)
+                            Log($"[Comp] 从 CurseForge 获取到了 {ProjectList.Count} 个工程(已获取 {Task.Input.Storage.CurseForgeOffset} 个,共 {Task.Input.Storage.CurseForgeTotal} 个)")
+                        Catch ex As Exception
+                            Log(ex, "从 CurseForge 获取工程列表失败")
+                            [Error] = ex
+                        End Try
+                    End Sub, "CurseForge Project Request")
+            End If
+
+            '启动 Modrinth 线程
+            Dim ModrinthUrl As String = Task.Input.GetModrinthAddress()
+            If ModrinthUrl IsNot Nothing Then
+                ModrinthThread = RunInNewThread(
+                    Sub()
+                        Try
+                            Log("[Comp] 开始从 Modrinth 获取工程列表:" & ModrinthUrl)
+                            Dim RequestResult As JObject = NetGetCodeByRequestRetry(ModrinthUrl, IsJson:=True, Encode:=Encoding.UTF8)
+                            Task.Progress += 0.2
+                            Dim ProjectList As New List(Of CompProject)
+                            For Each JsonEntry As JObject In RequestResult("hits")
+                                ProjectList.Add(New CompProject(JsonEntry))
+                            Next
+                            '更新结果
+                            SyncLock ResultsLock
+                                RawResults.AddRange(ProjectList)
+                            End SyncLock
+                            Task.Input.Storage.ModrinthOffset += ProjectList.Count
+                            Task.Input.Storage.ModrinthTotal = RequestResult("total_hits").ToObject(Of Integer)
+                            Log($"[Comp] 从 Modrinth 获取到了 {ProjectList.Count} 个工程(已获取 {Task.Input.Storage.ModrinthOffset} 个,共 {Task.Input.Storage.ModrinthTotal} 个)")
+                        Catch ex As Exception
+                            Log(ex, "从 Modrinth 获取工程列表失败")
+                            [Error] = ex
+                        End Try
+                    End Sub, "Modrinth Project Request")
+            End If
+
+            '等待线程结束
+            If CurseForgeThread IsNot Nothing Then CurseForgeThread.Join()
+            If Task.IsAborted Then Exit Sub '会自动触发 Finally
+            If ModrinthThread IsNot Nothing Then ModrinthThread.Join()
+
+            '确认存在结果
+            If RawResults.Count = 0 Then
+                If [Error] IsNot Nothing Then
+                    Throw [Error]
+                Else
+                    Throw New Exception("没有符合条件的结果")
+                End If
+            End If
+
+        Finally
+            If CurseForgeThread IsNot Nothing Then CurseForgeThread.Interrupt()
+            If ModrinthThread IsNot Nothing Then ModrinthThread.Interrupt()
+        End Try
+
+#End Region
+
+#Region "提取非重复项,存储于 RealResults"
+
+        '将 CurseForge 排在 Modrinth 的前面,避免加载结束顺序不同导致排名不同
+        RawResults = RawResults.Where(Function(x) x.FromCurseForge).Concat(RawResults.Where(Function(x) Not x.FromCurseForge)).ToList
+        'RawResults 去重
+        RawResults = Distinct(RawResults, Function(a, b) a.IsLike(b))
+        '已有内容去重
+        RawResults = RawResults.Where(Function(r) Not RealResults.Any(Function(b) r.IsLike(b)) AndAlso
+                                                  Not Task.Input.Storage.Results.Any(Function(b) r.IsLike(b))).ToList
+        '加入列表
+        RealResults.AddRange(RawResults)
+        Log($"[Comp] 去重后累计新增结果 {RealResults.Count} 个")
+
+#End Region
+
+#Region "检查结果数量,如果不足且可继续,会继续加载下一页"
+
+        If RealResults.Count + Task.Input.Storage.Results.Count < Task.Input.TargetResultCount Then
+            Log($"[Comp] 总结果数需求最少 {Task.Input.TargetResultCount} 个,仅获得了 {RealResults.Count + Task.Input.Storage.Results.Count} 个")
+            If Task.Input.CanContinue Then
+                Log("[Comp] 将继续尝试加载下一页")
+                GoTo Retry
+            Else
+                Log("[Comp] 无法继续加载,将强制结束")
+            End If
+        End If
+
+#End Region
+
+#Region "将结果排序并添加"
+
+        Dim Scores As New Dictionary(Of CompProject, Double) '排序分
+        If String.IsNullOrEmpty(Task.Input.SearchText) Then
+            '如果没有搜索文本,按下载量将结果排序
+            For Each Result As CompProject In RealResults
+                Scores.Add(Result, Result.DownloadCount * If(Result.FromCurseForge, 1, 30))
+            Next
+        Else
+            '如果有搜索文本,按关联度将结果排序
+            '排序分 = 搜索相对相似度 (1) + 下载量权重 (对数,10 亿时为 1) + 有中文名 (0.2)
+            Dim Entry As New List(Of SearchEntry(Of CompProject))
+            For Each Result As CompProject In RealResults
+                Scores.Add(Result, If(Result.WikiId > 0, 0.2, 0) +
+                           Math.Log10(Math.Max(Result.DownloadCount, 1) * If(Result.FromCurseForge, 1, 30)) / 9)
+                Entry.Add(New SearchEntry(Of CompProject) With {.Item = Result, .SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
+                          New KeyValuePair(Of String, Double)(If(IsChineseSearch, Result.TranslatedName, Result.RawName), 1),
+                          New KeyValuePair(Of String, Double)(Result.Description, 0.05)}})
+            Next
+            Dim SearchResult = Search(Entry, RawFilter, 101, -1)
+            For Each OneResult In SearchResult
+                Scores(OneResult.Item) += OneResult.Similarity / SearchResult(0).Similarity '最高 1 分的相似度分
+            Next
+        End If
+        '根据排序分得出结果并添加
+        Task.Input.Storage.Results.AddRange(
+            Sort(Scores.ToList, Function(a, b) a.Value > b.Value).Select(Function(r) r.Key).ToList)
+
+#End Region
+
+    End Sub
+
+#End Region
+
+#Region "CompFile | 文件信息"
+
+    '类定义
+
+    Public Enum CompFileStatus
+        Release = 1 '枚举值来源:https://docs.curseforge.com/#tocS_FileReleaseType
+        Beta = 2
+        Alpha = 3
+    End Enum
+    Public Class CompFile
+
+        '源信息
+
+        ''' <summary>
+        ''' 文件的种类。
+        ''' </summary>
+        Public ReadOnly Type As CompType
+        ''' <summary>
+        ''' 该文件来自 CurseForge 还是 Modrinth。
+        ''' </summary>
+        Public ReadOnly FromCurseForge As Boolean
+        ''' <summary>
+        ''' 用于唯一性鉴别该文件的 ID。CurseForge 中为 123456 的大整数,Modrinth 中为英文乱码的 Version 字段。
+        ''' </summary>
+        Public ReadOnly Id As String
+
+        '描述性信息
+
+        ''' <summary>
+        ''' 文件描述名(并非文件名,是自定义的字段)。
+        ''' </summary>
+        Public DisplayName As String
+        ''' <summary>
+        ''' 发布时间。
+        ''' </summary>
+        Public ReadOnly ReleaseDate As Date
+        ''' <summary>
+        ''' 下载量计数。注意,该计数仅为一个来源,无法反应两边加起来的下载量,且 CurseForge 可能错误地返回 0。
+        ''' </summary>
+        Public ReadOnly DownloadCount As Integer
+        ''' <summary>
+        ''' 支持的 Mod 加载器列表。可能为空。
+        ''' </summary>
+        Public ReadOnly ModLoaders As List(Of CompModLoaderType)
+        ''' <summary>
+        ''' 支持的游戏版本列表。类型包括:"1.18.5","1.18","1.18 快照","21w15a","未知版本"。
+        ''' </summary>
+        Public ReadOnly GameVersions As List(Of String)
+        ''' <summary>
+        ''' 发布状态:Release/Beta/Alpha。
+        ''' </summary>
+        Public ReadOnly Status As CompFileStatus
+        ''' <summary>
+        ''' 发布状态的友好描述。例如:"正式版","Beta 版"。
+        ''' </summary>
+        Public ReadOnly Property StatusDescription As String
+            Get
+                Select Case Status
+                    Case CompFileStatus.Release
+                        Return "正式版"
+                    Case CompFileStatus.Beta
+                        Return If(ModeDebug, "Beta 版", "测试版")
+                    Case Else
+                        Return If(ModeDebug, "Alpha 版", "测试版")
+                End Select
+            End Get
+        End Property
+
+        '下载信息
+
+        ''' <summary>
+        ''' 下载的文件名。
+        ''' </summary>
+        Public ReadOnly FileName As String
+        ''' <summary>
+        ''' 文件所有可能的下载源。
+        ''' </summary>
+        Private ReadOnly DownloadAddress As String()
+        ''' <summary>
+        ''' 该文件的所有依赖工程的原始 ID。
+        ''' 这些 ID 可能没有加载,在加载后会添加到 Dependencies 中(主要是因为 Modrinth 返回的是字符串 ID 而非 Slug,导致 Project.Id 查询不到)。
+        ''' </summary>
+        Public ReadOnly RawDependencies As New List(Of String)
+        ''' <summary>
+        ''' 该文件的所有依赖工程的 Project.Id。
+        ''' </summary>
+        Public ReadOnly Dependencies As New List(Of String)
+        ''' <summary>
+        ''' 获取下载信息。
+        ''' </summary>
+        ''' <param name="LocalAddress">目标本地文件夹,或完整的文件路径。会自动判断类型。</param>
+        Public Function ToNetFile(LocalAddress As String) As NetFile
+            Return New NetFile(DownloadAddress, LocalAddress & If(LocalAddress.EndsWith("\"), FileName, ""))
+        End Function
+
+        '实例化
+
+        ''' <summary>
+        ''' 从文件 Json 中初始化实例。若出错会抛出异常。
+        ''' </summary>
+        Public Sub New(Data As JObject, Type As CompType)
+            Me.Type = Type
+            FromCurseForge = Data.ContainsKey("gameId")
+            If FromCurseForge Then
+#Region "CurseForge"
+                '简单信息
+                Id = Data("id")
+                DisplayName = Data("displayName").ToString.Replace("	", "").Trim(" ")
+                ReleaseDate = Data("fileDate")
+                Status = CType(Data("releaseType").ToObject(Of Integer), CompFileStatus)
+                DownloadCount = Data("downloadCount")
+                FileName = Data("fileName")
+                'DownloadAddress
+                Dim Url = Data("downloadUrl").ToString
+                If Url = "" Then Url = "https://media.forgecdn.net/files/" & Id.ToString.Substring(0, 4) & "/" & Id.ToString.Substring(4).TrimStart("0") & "/" & FileName
+                Url = Url.Replace(FileName, Net.WebUtility.UrlEncode(FileName)) '对文件名进行编码
+                DownloadAddress = {Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge", "://media"),
+                                   Url.Replace("-service.overwolf.wtf", ".forgecdn.net"),
+                                   Url.Replace("://edge", "://media"),
+                                   Url}.Distinct.ToArray
+                'Dependencies
+                If Type = CompType.Mod Then
+                    RawDependencies = Data("dependencies").
+                        Where(Function(d) d("relationType").ToObject(Of Integer) = 3 AndAlso '种类为依赖
+                                          d("modId").ToObject(Of Integer) <> 306612 AndAlso d("modId").ToObject(Of Integer) <> 634179). '排除 Fabric API 和 Quilt API
+                        Select(Function(d) d("modId").ToString).ToList
+                End If
+                'GameVersions
+                Dim RawVersions As List(Of String) = Data("gameVersions").Select(Function(t) t.ToString.Trim.ToLower).ToList
+                GameVersions = RawVersions.Where(Function(v) v.StartsWith("1.")).Select(Function(v) v.Replace("-snapshot", " 快照")).ToList
+                If GameVersions.Count > 1 Then
+                    GameVersions = Sort(GameVersions, AddressOf VersionSortBoolean).ToList
+                    If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)}
+                ElseIf GameVersions.Count = 1 Then
+                    GameVersions = GameVersions.ToList
+                Else
+                    GameVersions = New List(Of String) From {"未知版本"}
+                End If
+                'ModLoaders
+                ModLoaders = New List(Of CompModLoaderType)
+                If RawVersions.Contains("forge") Then ModLoaders.Add(CompModLoaderType.Forge)
+                If RawVersions.Contains("fabric") Then ModLoaders.Add(CompModLoaderType.Fabric)
+                If RawVersions.Contains("quilt") Then ModLoaders.Add(CompModLoaderType.Quilt)
+#End Region
+            Else
+#Region "Modrinth"
+                '简单信息
+                Id = Data("id")
+                DisplayName = Data("name").ToString.Replace("	", "").Trim(" ")
+                ReleaseDate = Data("date_published")
+                Status = If(Data("version_type").ToString = "release", CompFileStatus.Release, If(Data("version_type").ToString = "beta", CompFileStatus.Beta, CompFileStatus.Alpha))
+                DownloadCount = Data("downloads")
+                FileName = Data("files")(0)("filename")
+                DownloadAddress = {Data("files")(0)("url").ToString.Replace(FileName, Net.WebUtility.UrlEncode(FileName))}
+                'Dependencies
+                If Type = CompType.Mod Then
+                    RawDependencies = Data("dependencies").
+                        Where(Function(d) d("dependency_type") = "required" AndAlso '种类为依赖
+                                          d("project_id") <> "P7dR8mSH" AndAlso d("project_id") <> "qvIfYCYJ" AndAlso '排除 Fabric API 和 Quilt API
+                                          d("project_id").ToString.Length > 0). '有时候真的会空……
+                        Select(Function(d) d("project_id").ToString).ToList
+                End If
+                'GameVersions
+                Dim RawVersions As List(Of String) = Data("game_versions").Select(Function(t) t.ToString.Trim.ToLower).ToList
+                GameVersions = RawVersions.Where(Function(v) v.StartsWith("1.")).
+                                           Select(Function(v) If(v.Contains("-"), v.Split("-").First & " 快照", v)).ToList
+                If GameVersions.Count > 1 Then
+                    GameVersions = Sort(GameVersions, AddressOf VersionSortBoolean).ToList
+                    If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)}
+                ElseIf GameVersions.Count = 1 Then
+                    GameVersions = GameVersions.ToList
+                ElseIf RawVersions.Any(Function(v) RegexCheck(v, "[0-9]{2}w[0-9]{2}[a-z]{1}")) Then
+                    GameVersions = RawVersions.Where(Function(v) RegexCheck(v, "[0-9]{2}w[0-9]{2}[a-z]{1}")).ToList
+                Else
+                    GameVersions = New List(Of String) From {"未知版本"}
+                End If
+                'ModLoaders
+                Dim RawLoaders As List(Of String) = Data("loaders").Select(Function(v) v.ToString).ToList
+                ModLoaders = New List(Of CompModLoaderType)
+                If RawLoaders.Contains("forge") Then ModLoaders.Add(CompModLoaderType.Forge)
+                If RawLoaders.Contains("fabric") Then ModLoaders.Add(CompModLoaderType.Fabric)
+                If RawLoaders.Contains("quilt") Then ModLoaders.Add(CompModLoaderType.Quilt)
+#End Region
+            End If
+        End Sub
+        ''' <summary>
+        ''' 将当前文件信息实例化为控件。
+        ''' </summary>
+        Public Function ToListItem(OnClick As MyListItem.ClickEventHandler, Optional OnSaveClick As MyIconButton.ClickEventHandler = Nothing) As MyListItem
+
+            '获取描述信息
+            Dim Info As String = ""
+            Select Case Type
+                Case CompType.Mod
+                    Info += If(ModLoaders.Count > 0, Join(ModLoaders.Select(Function(m) GetStringFromEnum(m)).ToList, "/"), "") &
+                            If(GameVersions.Count > 1 AndAlso GameVersions.Count < 10, If(ModLoaders.Count > 0, " ", "") & Join(GameVersions, "、").Replace("-snapshot", " 快照"), "")
+                    If Info <> "" Then Info = "适用于 " & Info & ","
+                    Info += If(ModeDebug AndAlso Dependencies.Count > 0, Dependencies.Count & " 个前置 Mod,", "")
+                Case CompType.ModPack
+                    If GameVersions.All(Function(v) v.Contains("w")) Then
+                        Info += $"游戏版本 {Join(GameVersions, "、")},"
+                    End If
+            End Select
+            If DownloadCount > 0 Then 'CurseForge 的下载次数经常错误地返回 0
+                Info += If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次下载,", DownloadCount & " 次下载,")
+            End If
+            Info += GetTimeSpanString(ReleaseDate - Date.Now) & "更新"
+            Info += If(Status = CompFileStatus.Release, "", "," & StatusDescription)
+
+            '建立控件
+            Dim NewItem As New MyListItem With {
+                .Title = DisplayName, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Me,
+                .Info = Info
+            }
+            Select Case Status
+                Case CompFileStatus.Release
+                    NewItem.Logo = "pack://application:,,,/images/Icons/R.png"
+                Case CompFileStatus.Beta
+                    NewItem.Logo = "pack://application:,,,/images/Icons/B.png"
+                Case Else 'Alpha
+                    NewItem.Logo = "pack://application:,,,/images/Icons/A.png"
+            End Select
+            AddHandler NewItem.Click, OnClick
+
+            '建立另存为按钮
+            If OnSaveClick IsNot Nothing Then
+                Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"}
+                ToolTipService.SetPlacement(BtnSave, Primitives.PlacementMode.Center)
+                ToolTipService.SetVerticalOffset(BtnSave, 30)
+                ToolTipService.SetHorizontalOffset(BtnSave, 2)
+                AddHandler BtnSave.Click, OnSaveClick
+                NewItem.Buttons = {BtnSave}
+                NewItem.PaddingRight = 35
+            End If
+
+            '结束
+            Return NewItem
+        End Function
+
+        '辅助函数
+
+        Public Overrides Function ToString() As String
+            Return $"{Id}: {DisplayName}"
+        End Function
+
+    End Class
+
+    '获取
+
+    ''' <summary>
+    ''' 已知文件信息的缓存。
+    ''' </summary>
+    Private CompFilesCache As New Dictionary(Of String, List(Of CompFile))
+    ''' <summary>
+    ''' 获取某个工程下的全部文件列表。
+    ''' 必须在工作线程执行,失败会抛出异常。
+    ''' </summary>
+    Public Function CompFilesGet(ProjectId As String, FromCurseForge As Boolean) As List(Of CompFile)
+        '使用缓存
+        If CompFilesCache.ContainsKey(ProjectId) Then Return CompFilesCache(ProjectId)
+        '获取工程对象
+        Dim TargetProject As CompProject
+        If CompProjectCache.ContainsKey(ProjectId) Then '存在缓存
+            TargetProject = CompProjectCache(ProjectId)
+        ElseIf FromCurseForge Then 'CurseForge
+            TargetProject = New CompProject(NetGetCodeByRequestRetry("https://api.curseforge.com/v1/mods/" & ProjectId, IsJson:=True, Encode:=Encoding.UTF8)("data"))
+        Else 'Modrinth
+            TargetProject = New CompProject(NetGetCodeByRequestRetry("https://api.modrinth.com/v2/project/" & ProjectId, IsJson:=True, Encode:=Encoding.UTF8))
+        End If
+        '获取工程对象的文件列表
+        Log("[Comp] 开始获取文件列表:" & ProjectId)
+        Dim ResultJsonArray As JArray
+        If FromCurseForge Then
+            'CurseForge
+            If TargetProject.Type = CompType.Mod Then 'Mod 使用每个版本最新的文件
+                ResultJsonArray = GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(TargetProject.CurseForgeFileIds, ",") & "]}", "application/json"))("data")
+            Else '否则使用全部文件
+                ResultJsonArray = NetGetCodeByRequestRetry($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999", Accept:="application/json", IsJson:=True)("data")
+            End If
+        Else
+            'Modrinth
+            ResultJsonArray = NetGetCodeByRequestRetry($"https://api.modrinth.com/v2/project/{ProjectId}/version", Accept:="application/json", IsJson:=True)
+        End If
+        CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Type)).ToList
+        '获取前置 Mod 列表
+        If TargetProject.Type <> CompType.Mod Then Return CompFilesCache(ProjectId)
+        Dim Deps As List(Of String) = CompFilesCache(ProjectId).
+            SelectMany(Function(f) f.RawDependencies).Distinct().ToList
+        Dim UndoneDeps = Deps.Where(Function(f) Not CompProjectCache.ContainsKey(f)).ToList
+        '获取前置 Mod 工程信息
+        If UndoneDeps.Count > 0 Then
+            Log($"[Comp] {ProjectId} 文件列表中还需要获取信息的前置 Mod:{Join(UndoneDeps, ",")}")
+            Dim Projects As JArray
+            If TargetProject.FromCurseForge Then
+                Projects = GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods",
+                    "POST", "{""modIds"": [" & Join(UndoneDeps, ",") & "]}", "application/json"))("data")
+            Else
+                Projects = NetGetCodeByRequestRetry($"https://api.modrinth.com/v2/projects?ids=[""{Join(UndoneDeps, """,""")}""]",
+                    Accept:="application/json", IsJson:=True)
+            End If
+            For Each Project In Projects
+                Dim NewProject As New CompProject(Project) '在 New 的时候就添加了缓存
+            Next
+        End If
+        '更新前置 Mod 信息
+        If Deps.Count > 0 Then
+            For Each DepProject In Deps.Select(Function(id) CompProjectCache(id))
+                For Each File In CompFilesCache(ProjectId)
+                    If File.RawDependencies.Contains(DepProject.Id) AndAlso DepProject.Id <> ProjectId Then
+                        File.Dependencies.Add(DepProject.Id)
+                    End If
+                Next
+            Next
+        End If
+        Return CompFilesCache(ProjectId)
+    End Function
+
+    ''' <summary>
+    ''' 预载包含大量 CompFile 的卡片,添加必要的元素和前置 Mod 列表。
+    ''' </summary>
+    Public Sub CompFilesCardPreload(Stack As StackPanel, Files As List(Of CompFile), OnClick As MyListItem.ClickEventHandler)
+        '获取卡片对应的前置 ID
+        '如果为整合包就不会有 Dependencies 信息,所以不用管
+        Dim Deps As List(Of String) = Files.SelectMany(Function(f) f.Dependencies).Distinct.ToList()
+        Deps.Sort()
+        If Deps.Count = 0 Then Exit Sub
+        Deps = Deps.Where(Function(dep)
+                              If Not CompProjectCache.ContainsKey(dep) Then Log($"[Comp] 未找到 ID {dep} 的前置 Mod 信息", LogLevel.Debug)
+                              Return CompProjectCache.ContainsKey(dep)
+                          End Function).ToList
+        '添加开头间隔
+        Stack.Children.Add(New TextBlock With {.Text = "前置 Mod", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 2, 0, 5)})
+        '添加前置 Mod 列表
+        For Each Dep In Deps
+            Dim Item = CompProjectCache(Dep).ToCompItem(False, True, AddressOf FrmDownloadMod.ProjectClick)
+            Stack.Children.Add(Item)
+        Next
+        '添加结尾间隔
+        Stack.Children.Add(New TextBlock With {.Text = "可选版本", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 12, 0, 5)})
+    End Sub
+
+#End Region
+
+End Module

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

@@ -63,7 +63,7 @@
         End Try
         End Try
         PossibleLogs.Add(VersionPathIndie & "logs\latest.log") 'Minecraft 日志
         PossibleLogs.Add(VersionPathIndie & "logs\latest.log") 'Minecraft 日志
         PossibleLogs.Add(VersionPathIndie & "logs\debug.log") 'Minecraft Debug 日志
         PossibleLogs.Add(VersionPathIndie & "logs\debug.log") 'Minecraft Debug 日志
-        PossibleLogs = ArrayNoDouble(PossibleLogs)
+        PossibleLogs = PossibleLogs.Distinct.ToList
 
 
         '确定最新的日志文件
         '确定最新的日志文件
         Dim RightLogs As New List(Of String)
         Dim RightLogs As New List(Of String)
@@ -238,7 +238,7 @@
                         '创建文件名词典
                         '创建文件名词典
                         Dim FileNameDict As New Dictionary(Of String, KeyValuePair(Of String, String()))
                         Dim FileNameDict As New Dictionary(Of String, KeyValuePair(Of String, String()))
                         For Each SelectedFile In SelectedFiles
                         For Each SelectedFile In SelectedFiles
-                            DictionaryAdd(FileNameDict, GetFileNameFromPath(SelectedFile.Key).ToLower, SelectedFile)
+                            FileNameDict(GetFileNameFromPath(SelectedFile.Key).ToLower) = SelectedFile
                             OutputFiles.Add(SelectedFile.Key)
                             OutputFiles.Add(SelectedFile.Key)
                             Log("[Crash] 输出报告:" & SelectedFile.Key & ",作为 Minecraft 或启动器日志")
                             Log("[Crash] 输出报告:" & SelectedFile.Key & ",作为 Minecraft 或启动器日志")
                         Next
                         Next
@@ -399,7 +399,7 @@
                         Keywords.AddRange(AnalyzeStackKeyword(Fatal))
                         Keywords.AddRange(AnalyzeStackKeyword(Fatal))
                     Next
                     Next
                     If Keywords.Count > 0 Then
                     If Keywords.Count > 0 Then
-                        AppendReason(CrashReason.MC日志堆栈分析发现关键字, ArrayNoDouble(Keywords))
+                        AppendReason(CrashReason.MC日志堆栈分析发现关键字, Keywords.Distinct.ToList)
                         GoTo Done
                         GoTo Done
                     End If
                     End If
                 End If
                 End If
@@ -429,7 +429,7 @@ Done:
         If CrashReasons.ContainsKey(Reason) Then
         If CrashReasons.ContainsKey(Reason) Then
             If Additional IsNot Nothing Then
             If Additional IsNot Nothing Then
                 CrashReasons(Reason).AddRange(Additional)
                 CrashReasons(Reason).AddRange(Additional)
-                CrashReasons(Reason) = ArrayNoDouble(CrashReasons(Reason))
+                CrashReasons(Reason) = CrashReasons(Reason).Distinct.ToList
             End If
             End If
         Else
         Else
             CrashReasons.Add(Reason, New List(Of String)(If(Additional, {})))
             CrashReasons.Add(Reason, New List(Of String)(If(Additional, {})))
@@ -464,6 +464,8 @@ 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.block.model.BakedQuad.<init>(int[], int, net.minecraft.core.Direction, net.minecraft.client.renderer.texture.TextureAtlasSprite, boolean, boolean)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
+            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("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版本过高)
@@ -584,7 +586,7 @@ Done:
             PossibleStacks.Add(Stack.Trim) '.Split("$").First)
             PossibleStacks.Add(Stack.Trim) '.Split("$").First)
 NextStack:
 NextStack:
         Next
         Next
-        PossibleStacks = ArrayNoDouble(PossibleStacks)
+        PossibleStacks = PossibleStacks.Distinct.ToList
         Log("[Crash] 找到 " & PossibleStacks.Count & " 条可能的堆栈信息")
         Log("[Crash] 找到 " & PossibleStacks.Count & " 条可能的堆栈信息")
         If PossibleStacks.Count = 0 Then Return New List(Of String)
         If PossibleStacks.Count = 0 Then Return New List(Of String)
         For Each Stack As String In PossibleStacks
         For Each Stack As String In PossibleStacks
@@ -608,7 +610,7 @@ NextStack:
                 PossibleWords.Add(Word.Trim)
                 PossibleWords.Add(Word.Trim)
             Next
             Next
         Next
         Next
-        PossibleWords = ArrayNoDouble(PossibleWords)
+        PossibleWords = PossibleWords.Distinct.ToList
         Log("[Crash] 从堆栈信息中找到 " & PossibleWords.Count & " 个可能的 Mod ID 关键词")
         Log("[Crash] 从堆栈信息中找到 " & PossibleWords.Count & " 个可能的 Mod ID 关键词")
         If PossibleWords.Count > 0 Then Log("[Crash]  - " & Join(PossibleWords, ", "))
         If PossibleWords.Count > 0 Then Log("[Crash]  - " & Join(PossibleWords, ", "))
         If PossibleWords.Count > 10 Then
         If PossibleWords.Count > 10 Then
@@ -666,7 +668,7 @@ NextStack:
                 Exit For
                 Exit For
             Next
             Next
         Next
         Next
-        HintLines = ArrayNoDouble(HintLines)
+        HintLines = HintLines.Distinct.ToList
         Log("[Crash] 找到 " & HintLines.Count & " 个可能的崩溃 Mod 匹配行")
         Log("[Crash] 找到 " & HintLines.Count & " 个可能的崩溃 Mod 匹配行")
         For Each ModName As String In HintLines
         For Each ModName As String In HintLines
             Log("[Crash]  - " & ModName)
             Log("[Crash]  - " & ModName)
@@ -682,7 +684,7 @@ NextStack:
             End If
             End If
             If Name IsNot Nothing Then ModFileNames.Add(Name)
             If Name IsNot Nothing Then ModFileNames.Add(Name)
         Next
         Next
-        ModFileNames = ArrayNoDouble(ModFileNames)
+        ModFileNames = ModFileNames.Distinct.ToList
         Log("[Crash] 找到 " & ModFileNames.Count & " 个可能的崩溃 Mod 文件名")
         Log("[Crash] 找到 " & ModFileNames.Count & " 个可能的崩溃 Mod 文件名")
         For Each ModFileName As String In ModFileNames
         For Each ModFileName As String In ModFileNames
             Log("[Crash]  - " & ModFileName)
             Log("[Crash]  - " & ModFileName)

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

@@ -335,7 +335,7 @@
         ''' </summary>
         ''' </summary>
         Public ReleaseTime As String
         Public ReleaseTime As String
         ''' <summary>
         ''' <summary>
-        ''' 需要的最低 Forge 版本。空字符串为无限制,“28.1.56” 表示版本号,“1161” 表示版本号的最后一位。
+        ''' 需要的最低 Forge 版本。空字符串为无限制,Nothing 为不兼容,“28.1.56” 表示版本号,“1161” 表示版本号的最后一位。
         ''' </summary>
         ''' </summary>
         Public RequiredForgeVersion As String
         Public RequiredForgeVersion As String
     End Class
     End Class
@@ -389,6 +389,7 @@
                          .Inherit = Name(i).ToString.Split(" ")(0),
                          .Inherit = Name(i).ToString.Split(" ")(0),
                          .NameFile = If(Name(i).ToLower.Contains("pre"), "preview_", "") & "OptiFine_" & Name(i).Replace(" ", "_") & ".jar",
                          .NameFile = If(Name(i).ToLower.Contains("pre"), "preview_", "") & "OptiFine_" & Name(i).Replace(" ", "_") & ".jar",
                          .RequiredForgeVersion = Forge(i).Replace("Forge ", "").Replace("#", "")}
                          .RequiredForgeVersion = Forge(i).Replace("Forge ", "").Replace("#", "")}
+            If Entry.RequiredForgeVersion.Contains("N/A") Then Entry.RequiredForgeVersion = Nothing
             Entry.NameVersion = Entry.Inherit & "-OptiFine_" & Name(i).ToString.Replace(" ", "_").Replace(Entry.Inherit & "_", "")
             Entry.NameVersion = Entry.Inherit & "-OptiFine_" & Name(i).ToString.Replace(" ", "_").Replace(Entry.Inherit & "_", "")
             Versions.Add(Entry)
             Versions.Add(Entry)
         Next
         Next
@@ -413,6 +414,7 @@
                              .NameFile = Token("filename").ToString,
                              .NameFile = Token("filename").ToString,
                              .RequiredForgeVersion = If(Token("forge"), "").ToString.Replace("Forge ", "").Replace("#", "")
                              .RequiredForgeVersion = If(Token("forge"), "").ToString.Replace("Forge ", "").Replace("#", "")
                          }
                          }
+                If Entry.RequiredForgeVersion.Contains("N/A") Then Entry.RequiredForgeVersion = Nothing
                 Entry.NameVersion = Entry.Inherit & "-OptiFine_" & (Token("type").ToString & " " & Token("patch").ToString).Replace(".0 ", " ").Replace(" ", "_").Replace(Entry.Inherit & "_", "")
                 Entry.NameVersion = Entry.Inherit & "-OptiFine_" & (Token("type").ToString & " " & Token("patch").ToString).Replace(".0 ", " ").Replace(" ", "_").Replace(Entry.Inherit & "_", "")
                 Versions.Add(Entry)
                 Versions.Add(Entry)
             Next
             Next
@@ -944,719 +946,14 @@
     ''' <summary>
     ''' <summary>
     ''' Fabric API 列表,官方源。
     ''' Fabric API 列表,官方源。
     ''' </summary>
     ''' </summary>
-    Public DlFabricApiLoader As New LoaderTask(Of Integer, List(Of DlCfFile))("Fabric API List Loader",
-        Sub(Task As LoaderTask(Of Integer, List(Of DlCfFile))) Task.Output = DlCfGetFiles(306612, False))
+    Public DlFabricApiLoader As New LoaderTask(Of Integer, List(Of CompFile))("Fabric API List Loader",
+        Sub(Task As LoaderTask(Of Integer, List(Of CompFile))) Task.Output = CompFilesGet("fabric-api", False))
 
 
     ''' <summary>
     ''' <summary>
     ''' OptiFabric 列表,官方源。
     ''' OptiFabric 列表,官方源。
     ''' </summary>
     ''' </summary>
-    Public DlOptiFabricLoader As New LoaderTask(Of Integer, List(Of DlCfFile))("OptiFabric List Loader",
-        Sub(Task As LoaderTask(Of Integer, List(Of DlCfFile))) Task.Output = DlCfGetFiles(322385, False))
-
-#End Region
-
-#Region "DlCfProject | CurseForge 工程"
-
-    Private DlCfProjectDb As Dictionary(Of String, DlCfDbEntry) = Nothing
-    Private DlCfProjectCache As New Dictionary(Of Integer, DlCfProject)
-
-    ''' <summary>
-    ''' CurseForge 工程列表获取、搜索请求。
-    ''' </summary>
-    Public Class DlCfProjectRequest
-        Public CategoryId As Integer = 0 '421: Mod(?), 4780: Fabric
-        Public IsModPack As Boolean = False
-        Public GameVersion As String = Nothing
-        Public PageSize As Integer = 50
-        Public SearchFilter As String = Nothing
-        Public Index As Integer? = Nothing
-        Public ModLoader As Integer? = Nothing
-        Public Function GetAddress() As String
-            GetAddress = "https://api.curseforge.com/v1/mods/search?gameId=432&sortField=Featured&sortOrder=desc" &
-                         "&pageSize=" & PageSize &
-                         "&categoryId=" & CategoryId &
-                         If(IsModPack, "&classId=4471", "&classId=6") &
-                         If(String.IsNullOrEmpty(GameVersion), "", "&gameVersion=" & GameVersion) &
-                         If(String.IsNullOrEmpty(SearchFilter), "", "&searchFilter=" & Net.WebUtility.UrlEncode(SearchFilter)) &
-                         If(Index IsNot Nothing, "&index=" & Index, "") &
-                         If(ModLoader IsNot Nothing AndAlso ModLoader > 0, "&modLoaderType=" & ModLoader, "")
-        End Function
-
-#Region "相同判断"
-
-        Public Overrides Function Equals(obj As Object) As Boolean
-            Dim request = TryCast(obj, DlCfProjectRequest)
-            Return request IsNot Nothing AndAlso request.GetAddress = GetAddress()
-        End Function
-        Public Shared Operator =(left As DlCfProjectRequest, right As DlCfProjectRequest) As Boolean
-            Return EqualityComparer(Of DlCfProjectRequest).Default.Equals(left, right)
-        End Operator
-        Public Shared Operator <>(left As DlCfProjectRequest, right As DlCfProjectRequest) As Boolean
-            Return Not left = right
-        End Operator
-
-#End Region
-
-    End Class
-
-    ''' <summary>
-    ''' CurseForge 工程信息。
-    ''' </summary>
-    Public Class DlCfProject
-
-        Public Id As Integer
-        Public Name As String
-        Public Description As String
-        Public Website As String
-        Public DateUpdate As Date
-        Public DownloadCount As Integer
-        Public ModLoaders As New List(Of String)
-        Public GameVersionDesc As String
-        Private CategoryDesc As List(Of String)
-        Private Thumb As String
-        Public IsModPack As Boolean
-        Public FileIndexes As New List(Of Integer)
-        Public Files As List(Of DlCfFile)
-        Private _MCBBS As Integer?
-        Public Property MCBBS As Integer?
-            Get
-                ReleaseCfDatabase()
-                If _MCBBS Is Nothing Then
-                    If Website IsNot Nothing Then
-                        Dim Keyword As String = Website.TrimEnd("/").Split("/").Last
-                        If DlCfProjectDb.ContainsKey(Keyword) Then _MCBBS = DlCfProjectDb(Keyword).MCBBS
-                    End If
-                End If
-                Return _MCBBS
-            End Get
-            Set(value As Integer?)
-                _MCBBS = value
-            End Set
-        End Property
-        Private _ChineseName As String
-        Public Property ChineseName As String
-            Get
-                ReleaseCfDatabase()
-                If _ChineseName Is Nothing Then
-                    _ChineseName = Name
-                    If Website IsNot Nothing Then
-                        Dim Keyword As String = Website.TrimEnd("/").Split("/").Last
-                        If DlCfProjectDb.ContainsKey(Keyword) Then
-                            Dim Result = DlCfProjectDb(Keyword)
-                            If Not String.IsNullOrEmpty(Result.ChineseName) Then '使用原名
-                                _ChineseName = Result.ChineseName
-                            End If
-                        End If
-                    End If
-                End If
-                Return _ChineseName
-            End Get
-            Set(value As String)
-                _ChineseName = value
-            End Set
-        End Property
-        Private _WikiId As Integer = -1
-        ''' <summary>
-        ''' MCMOD 的页面 ID。若为 0 则代表没有对应页面。
-        ''' </summary>
-        Public Property WikiId As Integer
-            Get
-                ReleaseCfDatabase()
-                If _WikiId = -1 Then
-                    If Website IsNot Nothing Then
-                        Dim Keyword As String = Website.TrimEnd("/").Split("/").Last
-                        If DlCfProjectDb.ContainsKey(Keyword) Then
-                            _WikiId = DlCfProjectDb(Keyword).WikiId
-                        Else
-                            _WikiId = 0
-                        End If
-                    End If
-                End If
-                Return _WikiId
-            End Get
-            Set(value As Integer)
-                _WikiId = value
-            End Set
-        End Property
-
-        ''' <summary>
-        ''' 从 CurseForge 工程 Json 项中初始化实例。若出错会抛出异常。
-        ''' </summary>
-        Public Sub New(Data As JObject)
-            'https://api.curseforge.com/v1/mods/search?gameId=432&pageSize=5&categoryID=421
-            Id = Data("id")
-            Name = Data("name")
-            Description = Data("summary")
-            Website = Data("links")("websiteUrl")
-            DateUpdate = Data("dateModified")
-            DownloadCount = Data("downloadCount")
-            IsModPack = Not Website.Contains("/mc-mods/")
-            '获取 Mod Loader 列表
-            ModLoaders = New List(Of String)
-            For Each File In If(Data("latestFiles"), {})
-                Dim NewFile As New DlCfFile(File, IsModPack)
-                If Not NewFile.IsAvailable Then Continue For
-                ModLoaders.AddRange(NewFile.ModLoaders)
-            Next
-            ModLoaders = ModLoaders.Distinct.ToList
-            '用一个偷懒的方式让 Forge 排在 Fabric 的前面
-            ModLoaders = ModLoaders.Select(Function(s) s.Replace("Forge", "Aorge")).ToList()
-            ModLoaders.Sort()
-            ModLoaders = ModLoaders.Select(Function(s) s.Replace("Aorge", "Forge")).ToList()
-            '获取游戏版本
-            Dim GameVersions As New List(Of Integer)
-            For Each File In If(Data("latestFilesIndexes"), {})
-                Dim Version As String = File("gameVersion")
-                If Not Version.Contains("1.") Then Continue For
-                GameVersions.Add(Version.Split(".")(1).Split("-").First)
-                FileIndexes.Add(File("fileId"))
-            Next
-            GameVersions = Sort(GameVersions.Distinct.ToList, AddressOf VersionSortBoolean)
-            If GameVersions.Count = 0 Then
-                GameVersionDesc = ""
-            Else
-                Dim SpaVersions As New List(Of String)
-                For i = 0 To GameVersions.Count - 1
-                    Dim StartVersion As Integer = GameVersions(i), EndVersion As Integer = GameVersions(i)
-                    For ii = i + 1 To GameVersions.Count - 1
-                        If GameVersions(ii) = EndVersion - 1 Then
-                            EndVersion = GameVersions(ii)
-                            i = ii
-                        Else
-                            Exit For
-                        End If
-                    Next
-                    If StartVersion = EndVersion Then
-                        SpaVersions.Add("1." & StartVersion)
-                    ElseIf StartVersion >= McVersionHighest Then
-                        SpaVersions.Add("1." & EndVersion & "+")
-                    ElseIf EndVersion <= 7 Then
-                        SpaVersions.Add("1." & StartVersion & "-")
-                        Exit For
-                    ElseIf StartVersion - EndVersion = 1 Then
-                        SpaVersions.Add("1." & StartVersion & ", 1." & EndVersion)
-                    Else
-                        SpaVersions.Add("1." & StartVersion & "~1." & EndVersion)
-                    End If
-                Next
-                GameVersionDesc = "[" & Join(SpaVersions, ", ") & "] "
-            End If
-            '获取分类
-            Dim Categories = Data("categories")
-            Dim CategoryId As New List(Of Integer)
-            For Each CategoryToken In Categories
-                CategoryId.Add(CategoryToken("id"))
-            Next
-            CategoryId = ArrayNoDouble(CategoryId)
-            CategoryId.Sort()
-            CategoryDesc = New List(Of String)
-            For Each Category In CategoryId
-                Dim Desc As String
-                Select Case Category
-
-                    Case 406 : Desc = "世界生成"
-                    Case 407 : Desc = "生物群系"
-                    Case 409 : Desc = "天然结构"
-                    Case 410 : Desc = "维度"
-                    Case 411 : Desc = "生物"
-                    Case 412 : Desc = "科技"
-                    Case 414 : Desc = "交通与移动"
-                    Case 415 : Desc = "管道与物流"
-                    Case 417 : Desc = "能源"
-                    Case 4558 : Desc = "红石"
-                    Case 420 : Desc = "仓储"
-                    Case 416 : Desc = "农业"
-                    Case 436 : Desc = "食物"
-                    Case 419 : Desc = "魔法"
-                    Case 434 : Desc = "装备与工具"
-                    Case 422 : Desc = "冒险与探索"
-                    Case 424 : Desc = "建筑与装饰"
-                    Case 423 : Desc = "信息显示"
-                    Case 425 : Desc = "杂项"
-                    Case 421 : Desc = "支持库"
-                    Case 435 : Desc = "服务器"
-
-                    Case 4471 : Desc = "科幻"
-                    Case 4483 : Desc = "战斗"
-                    Case 4477 : Desc = "小游戏"
-                    Case 4478 : Desc = "任务"
-                    Case 4484 : Desc = "多人"
-                    Case 4476 : Desc = "探索"
-                    Case 4736 : Desc = "空岛"
-                    Case 4475 : Desc = "冒险"
-                    Case 4487 : Desc = "FTB"
-                    Case 4480 : Desc = "基于地图"
-                    Case 4479 : Desc = "硬核"
-                    Case 4472 : Desc = "科技"
-                    Case 4473 : Desc = "魔法"
-                    Case 4481 : Desc = "小型整合"
-                    Case 4482 : Desc = "大型整合"
-
-                    Case Else : Continue For
-                End Select
-                CategoryDesc.Add(Desc)
-            Next
-            If CategoryDesc.Count = 0 Then CategoryDesc.Add("杂类")
-            '获取图片
-            If Data("logo").Count > 0 Then
-                If GetPixelSize(1) > 1.25 Then
-                    Thumb = Data("logo")("thumbnailUrl").ToString '使用 256x256 图标
-                Else
-                    Thumb = Data("logo")("thumbnailUrl").ToString.Replace("/256/256/", "/64/64/") '使用 64x64 图标
-                End If
-            End If
-            '保存缓存
-            If Not DlCfProjectCache.ContainsKey(Id) OrElse DlCfProjectCache(Id).Files Is Nothing Then DictionaryAdd(DlCfProjectCache, Id, Me)
-        End Sub
-
-        Public Function ToCfItem(ShowVersionDesc As Boolean, ShowLoaderDesc As Boolean, Optional OnClick As MyCfItem.ClickEventHandler = Nothing) As MyCfItem
-            Dim NewItem As New MyCfItem With {.Tag = Me}
-            NewItem.LabTitle.Text = ChineseName
-            NewItem.LabInfo.Text = Description.Replace(vbCr, "").Replace(vbLf, "")
-            NewItem.LabLeft.Text = (If(ModLoaders.Count > 0 AndAlso ShowLoaderDesc, "[" & Join(ModLoaders, "/") & "] ", "") &
-                                   If(ShowVersionDesc, GameVersionDesc, "")).Replace("] [", " ") &
-                                   Join(CategoryDesc, ",") & " (" &
-                                   GetTimeSpanString(DateUpdate - Date.Now) & "更新," &
-                                   If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次下载", DownloadCount & " 次下载") & ")"
-            If Thumb Is Nothing Then
-                NewItem.Logo = "pack://application:,,,/images/Icons/NoIcon.png"
-            Else
-                NewItem.Logo = Thumb
-            End If
-            If OnClick IsNot Nothing Then
-                AddHandler NewItem.Click, OnClick
-                NewItem.HasAnimation = True
-            Else
-                NewItem.HasAnimation = False
-            End If
-            '结束
-            Return NewItem
-        End Function
-
-        Public Overrides Function Equals(obj As Object) As Boolean
-            Dim project = TryCast(obj, DlCfProject)
-            Return project IsNot Nothing AndAlso Id = project.Id
-        End Function
-        Public Shared Operator =(left As DlCfProject, right As DlCfProject) As Boolean
-            Return EqualityComparer(Of DlCfProject).Default.Equals(left, right)
-        End Operator
-        Public Shared Operator <>(left As DlCfProject, right As DlCfProject) As Boolean
-            Return Not left = right
-        End Operator
-    End Class
-
-    Public Class DlCfProjectResult
-        Public Projects As List(Of DlCfProject)
-        Public Index As Integer
-        Public RealCount As Long
-    End Class
-    ''' <summary>
-    ''' CurseForge 工程列表获取事件。
-    ''' </summary>
-    Public Sub DlCfProjectSub(Task As LoaderTask(Of DlCfProjectRequest, DlCfProjectResult))
-        '拒绝 1.13- Quilt(这个版本根本没有 Quilt)
-        If Not Task.Input.IsModPack AndAlso
-            If(Task.Input.ModLoader, 0) = 5 AndAlso VersionSortInteger(If(Task.Input.GameVersion, "1.15"), "1.14") = -1 Then
-            Throw New Exception("Quilt 不支持 Minecraft " & Task.Input.GameVersion)
-        End If
-
-        Dim RawFilter As String = If(Task.Input.SearchFilter, "").Trim
-        Task.Input.SearchFilter = RawFilter
-        RawFilter = RawFilter.ToLower
-        Log("[Download] CurseForge 工程列表搜索原始文本:" & RawFilter)
-        '中文请求关键字处理
-        Dim IsChineseSearch As Boolean = RegexCheck(RawFilter, "[\u4e00-\u9fbb]")
-        If IsChineseSearch AndAlso Not String.IsNullOrEmpty(RawFilter) Then
-            If Task.Input.IsModPack Then Throw New Exception("整合包搜索仅支持英文")
-            ReleaseCfDatabase()
-            '构造搜索请求
-            Dim SearchEntries As New List(Of SearchEntry(Of DlCfDbEntry))
-            For Each Entry In DlCfProjectDb.Values
-                If Entry.ChineseName.Contains("动态的树") Then Continue For '傻逼 Mod 的附属太多了
-                SearchEntries.Add(New SearchEntry(Of DlCfDbEntry) With {
-                    .Item = Entry,
-                    .SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
-                        New KeyValuePair(Of String, Double)(Entry.ChineseName & Entry.CurseForgeId, 1)
-                    }
-                })
-            Next
-            '获取搜索结果
-            Dim SearchResults = Search(SearchEntries, Task.Input.SearchFilter, 3)
-            If SearchResults.Count = 0 Then Throw New Exception("无搜索结果,请尝试搜索英文名称")
-            Dim SearchResult As String = ""
-            For i = 0 To Math.Min(4, SearchResults.Count - 1) '就算全是准确的,也最多只要 5 个
-                If Not SearchResults(i).AbsoluteRight AndAlso i >= Math.Min(2, SearchResults.Count - 1) Then Exit For '把 3 个结果拼合以提高准确度
-                SearchResult += SearchResults(i).Item.CurseForgeId.Replace("-", " ") & " "
-                SearchResult += SearchResults(i).Item.ChineseName.Replace(" (", "|").Split("|").Last.TrimEnd(") ").Replace(" - ", "§").Split("§").First & " "
-            Next
-            Log("[Download] CurseForge 中文搜索原始关键词:" & SearchResult, LogLevel.Developer)
-            '去除常见连接词
-            Dim RealFilter As String = ""
-            For Each Word In SearchResult.Split(" ")
-                If {"the", "of", "a", "mod"}.Contains(Word.ToLower) OrElse Val(Word) > 0 Then Continue For
-                If SearchResult.Split(" ").Count > 3 AndAlso {"ftb"}.Contains(Word.ToLower) Then Continue For
-                RealFilter += Word.TrimStart("{[(").TrimEnd("}])") & " "
-            Next
-            Task.Input.SearchFilter = RealFilter
-            Log("[Download] CurseForge 中文搜索最终关键词:" & RealFilter, LogLevel.Developer)
-        End If
-        '驼峰英文请求关键字处理
-        Dim SpacedKeywords = RegexReplace(Task.Input.SearchFilter, "$& ", "([A-Z]+|[a-z]+?)(?=[A-Z]+[a-z]+[a-z ]*)")
-        Dim ConnectedKeywords = Task.Input.SearchFilter.Replace(" ", "")
-        Dim AllPossibleKeywords = (SpacedKeywords & " " & If(IsChineseSearch, Task.Input.SearchFilter, ConnectedKeywords & " " & RawFilter)).ToLower
-        '最终处理关键字:分割、去重
-        Dim RightKeywords As New List(Of String)
-        For Each Keyword In AllPossibleKeywords.Split(" ")
-            If Keyword.Trim = "" Then Continue For
-            If {"forge", "fabric", "for", "mod",
-                "forge/fabric", "fabric/forge", "fabric/quilt", "quilt/fabric"}.Contains(Keyword) Then 'https://github.com/Hex-Dragon/PCL2/issues/208
-                Log("[Download] 已跳过搜索关键词:" & Keyword, LogLevel.Developer)
-                Continue For
-            End If
-            RightKeywords.Add(Keyword)
-        Next
-        If RawFilter.Length > 0 AndAlso RightKeywords.Count = 0 Then
-            Task.Input.SearchFilter = RawFilter '全都被过滤掉了
-        Else
-            Task.Input.SearchFilter = Join(ArrayNoDouble(RightKeywords), " ").ToLower
-        End If
-        '例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod)
-        If RawFilter.Replace(" ", "").ToLower.Contains("optiforge") Then Task.Input.SearchFilter = "optiforge"
-        If RawFilter.Replace(" ", "").ToLower.Contains("optifabric") Then Task.Input.SearchFilter = "optifabric"
-        Log("[Download] CurseForge 工程列表搜索最终文本:" & Task.Input.SearchFilter, LogLevel.Developer)
-        '正式获取
-        Dim Url = Task.Input.GetAddress()
-        Log("[Download] 开始获取 CurseForge 工程列表:" & Url)
-        Dim RequestResult As JObject = NetGetCodeByRequestRetry(Url, IsJson:=True, Encode:=Encoding.UTF8)
-        Dim FileList = GetCfProjectListFromJson(RequestResult("data"), Task.Input.IsModPack)
-        If FileList.Count = 0 Then Throw New Exception("无搜索结果")
-        '重新排序
-        If Not String.IsNullOrEmpty(Task.Input.SearchFilter) Then
-            '排序分 = 搜索相对相似度 + 默认排序相对活跃度
-            Dim Scores As New Dictionary(Of DlCfProject, Double)
-            Dim Entry As New List(Of SearchEntry(Of DlCfProject))
-            For i = 1 To FileList.Count
-                Dim File = FileList(i - 1)
-                Scores.Add(File, (1 - (i / FileList.Count)))
-                Entry.Add(New SearchEntry(Of DlCfProject) With {.Item = File, .SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
-                          New KeyValuePair(Of String, Double)(If(IsChineseSearch, File.ChineseName, File.Name), 1),
-                          New KeyValuePair(Of String, Double)(File.Description, 0.05)}})
-            Next
-            Dim SearchResult = Search(Entry, RawFilter, 101, -1)
-            For Each OneResult In SearchResult
-                Scores(OneResult.Item) += OneResult.Similarity / SearchResult(0).Similarity
-            Next
-            '根据排序分得出结果
-            Dim ResultList = Sort(Scores.ToList, Function(Left As KeyValuePair(Of DlCfProject, Double), Right As KeyValuePair(Of DlCfProject, Double))
-                                                     Return Left.Value > Right.Value
-                                                 End Function)
-            FileList = New List(Of DlCfProject)
-            For Each Result In ResultList
-                FileList.Add(Result.Key)
-            Next
-        End If
-        Task.Output = New DlCfProjectResult With {.Projects = FileList, .Index = RequestResult("pagination")("index"), .RealCount = RequestResult("pagination")("totalCount")}
-    End Sub
-    ''' <summary>
-    ''' 从 API 返回的 Json 数组中获取工程信息数组。
-    ''' </summary>
-    Private Function GetCfProjectListFromJson(Json As JArray, IsModPack As Boolean) As List(Of DlCfProject)
-        Dim FileList As New List(Of DlCfProject)
-        For Each JsonEntry As JObject In Json
-            Dim Result = GetCfProjectFromJson(JsonEntry, IsModPack)
-            If Result IsNot Nothing Then FileList.Add(Result)
-        Next
-        Return FileList
-    End Function
-    ''' <summary>
-    ''' 从 API 返回的 Json 中获取工程信息。
-    ''' </summary>
-    Private Function GetCfProjectFromJson(Json As JObject, IsModPack As Boolean) As DlCfProject
-        If Json("links")("websiteUrl") Is Nothing Then
-            Log("[Download] 发现关键项为 Nothing 的工程:" & If(Json, "").ToString, LogLevel.Debug)
-            Return Nothing
-        End If
-        If IsModPack = Json("links")("websiteUrl").ToString.Contains("curseforge.com/minecraft/mc-mods") Then
-            Log("[Download] 返回的工程与要求的类别不一致:" & Json("links")("websiteUrl").ToString, LogLevel.Debug)
-            Return Nothing
-        End If
-        Return New DlCfProject(Json)
-    End Function
-
-    ''' <summary>
-    ''' 读取 Mod 数据库。
-    ''' </summary>
-    Private Sub ReleaseCfDatabase()
-        If DlCfProjectDb IsNot Nothing Then Exit Sub
-        DlCfProjectDb = New Dictionary(Of String, DlCfDbEntry)
-        Dim i As Integer = 0
-        For Each Line In DecodeBytes(GetResources("ModData")).Split(vbLf)
-            i += 1
-            If Line = "" Then Continue For
-            For Each EntryData As String In Line.Split("¨")
-                Dim Entry = New DlCfDbEntry
-                Dim SplitedLine = EntryData.Split("|")
-                Entry.CurseForgeId = SplitedLine(0)
-                Entry.WikiId = i
-                If SplitedLine.Count >= 2 Then
-                    Entry.ChineseName = SplitedLine(1)
-                    Entry.MCBBS = If(SplitedLine.Count >= 3, SplitedLine(2), Nothing)
-                    If Entry.ChineseName.Contains("*") Then '处理 *
-                        Entry.ChineseName = Entry.ChineseName.Replace("*", " (" &
-                            String.Join(" ", Entry.CurseForgeId.Split("-").Select(Function(w) w.Substring(0, 1).ToUpper & w.Substring(1, w.Length - 1))) & ")")
-                    End If
-                End If
-                DlCfProjectDb(Entry.CurseForgeId) = Entry
-            Next
-        Next
-    End Sub
-    Public Class DlCfDbEntry
-        ''' <summary>
-        ''' McMod 的 ID。
-        ''' </summary>
-        Public WikiId As Integer
-        ''' <summary>
-        ''' 中文译名。若为空字符串则使用英文名。
-        ''' </summary>
-        Public ChineseName As String = ""
-        ''' <summary>
-        ''' MCBBS 发布帖编号(例如 195107)。若为 Nothing 则没有发布帖。
-        ''' </summary>
-        Public MCBBS As Integer?
-        ''' <summary>
-        ''' CurseForge ID(例如 advanced-solar-panels)。
-        ''' </summary>
-        Public CurseForgeId As String
-        Public Overrides Function ToString() As String
-            Return CurseForgeId & "|" & WikiId & "|" & ChineseName & If(MCBBS Is Nothing, "", "|" & MCBBS)
-        End Function
-    End Class
-
-#End Region
-
-#Region "DlCfFile | CurseForge Files"
-
-    Public DlCfFileLoader As New LoaderTask(Of KeyValuePair(Of Integer, Boolean), List(Of DlCfFile))("DlCfFile Main", Sub(Task As LoaderTask(Of KeyValuePair(Of Integer, Boolean), List(Of DlCfFile))) Task.Output = DlCfGetFiles(Task.Input.Key, Task.Input.Value))
-
-    ''' <summary>
-    ''' CurseForge 工程下的单一版本文件。
-    ''' </summary>
-    Public Class DlCfFile
-
-        Public IsModPack As Boolean
-        Public FileId As Integer
-        Public DisplayName As String
-        Public [Date] As Date
-        Public GameVersion As String()
-        Public ModLoaders As New List(Of String)
-        Public ReleaseType As Integer
-        Public FileName As String
-        Public DownloadCount As Integer
-        Public Dependencies As New List(Of Integer)
-        Private DownloadAddress As String()
-
-        Public ReadOnly Property ReleaseTypeString As String
-            Get
-                Select Case ReleaseType
-                    Case 1
-                        Return "正式版"
-                    Case 2
-                        Return If(ModeDebug, "Beta 测试版", "测试版")
-                    Case Else
-                        Return If(ModeDebug, "Alpha 测试版", "测试版")
-                End Select
-            End Get
-        End Property
-        Public ReadOnly Property IsAvailable As Boolean
-            Get
-                Return DisplayName IsNot Nothing AndAlso GameVersion IsNot Nothing AndAlso FileName IsNot Nothing AndAlso DownloadAddress(0).StartsWith("http")
-            End Get
-        End Property
-
-        ''' <summary>
-        ''' 从 CurseForge 文件 Json 项中初始化实例。若出错会抛出异常。
-        ''' </summary>
-        Public Sub New(Data As JObject, IsModPack As Boolean)
-            FileId = Data("id")
-            DisplayName = Data("displayName").ToString.Replace("	", "").Trim(" ")
-            [Date] = Data("fileDate")
-            ReleaseType = Data("releaseType")
-            DownloadCount = Data("downloadCount")
-            FileName = Data("fileName")
-            Me.IsModPack = IsModPack
-            '获取下载地址
-            Dim Url = Data("downloadUrl").ToString
-            If Url = "" Then Url = "https://media.forgecdn.net/files/" & FileId.ToString.Substring(0, 4) & "/" & FileId.ToString.Substring(4).TrimStart("0") & "/" & FileName
-            Url = Url.Replace(FileName, Net.WebUtility.UrlEncode(FileName)) '对文件名进行编码
-            DownloadAddress = ArrayNoDouble({Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge", "://media"), Url.Replace("-service.overwolf.wtf", ".forgecdn.net"), Url.Replace("://edge", "://media"), Url})
-            '获取前置 Mod 的 Addon ID
-            If Not IsModPack Then
-                For Each Dep In Data("dependencies")
-                    If Dep("relationType").ToObject(Of Integer) = 3 AndAlso '是一个依赖
-                        Dep("modId").ToObject(Of Integer) <> 306612 Then '排除 Fabric API
-                        Dependencies.Add(Dep("modId"))
-                    End If
-                Next
-            End If
-            '获取游戏版本与 Mod 加载器列表
-            Dim Versions As New List(Of String)
-            For Each Version In Data("gameVersions").Select(Function(t) t.ToString.Trim.ToLower)
-                If Version.StartsWith("1.") OrElse Version.Contains("w") Then
-                    Versions.Add(Version.Replace("-snapshot", " 快照"))
-                ElseIf Version = "forge" OrElse Version = "fabric" OrElse Version = "quilt" OrElse Version = "rift" Then
-                    ModLoaders.Add(Version.First.ToString.ToUpper & Version.Substring(1))
-                End If
-            Next
-            If Versions.Count > 1 Then
-                GameVersion = Sort(Versions, AddressOf VersionSortBoolean).ToArray
-                If IsModPack Then GameVersion = {GameVersion(0)}
-            ElseIf Versions.Count = 1 Then
-                GameVersion = Versions.ToArray
-            Else
-                GameVersion = {"未知版本"}
-            End If
-            '用一个偷懒的方式让 Forge 排在 Fabric 的前面
-            ModLoaders = ModLoaders.Select(Function(s) s.Replace("Forge", "Aorge")).ToList()
-            ModLoaders.Sort()
-            ModLoaders = ModLoaders.Select(Function(s) s.Replace("Aorge", "Forge")).ToList()
-        End Sub
-
-        ''' <summary>
-        ''' 获取对应的下载文件信息。
-        ''' </summary>
-        ''' <param name="LocalAddress">本地文件夹或文件地址。</param>
-        Public Function GetDownloadFile(LocalAddress As String, IsFullPath As Boolean) As NetFile
-            Return New NetFile(DownloadAddress, LocalAddress & If(IsFullPath, "", FileName))
-        End Function
-
-        Public Function ToListItem(OnClick As MyListItem.ClickEventHandler, Optional OnSaveClick As MyIconButton.ClickEventHandler = Nothing) As MyListItem
-
-            '获取描述信息
-            Dim Info As String = ""
-            If Not IsModPack Then
-                Info += If(ModLoaders.Count > 0, Join(ModLoaders, "/"), "") &
-                        If(GameVersion.Count > 1, If(ModLoaders.Count > 0, " ", "") & Join(GameVersion, "、").Replace("-snapshot", " 快照"), "")
-                If Info <> "" Then Info = "适用于 " & Info & ","
-                Info += If(ModeDebug AndAlso Dependencies.Count > 0, Dependencies.Count & " 个前置 Mod,", "")
-            End If
-            'If DownloadCount > 0 Then 'DownloadCount 目前的返回内容完全不正确,经常是 0,干脆不显示了
-            '    Info += If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次下载,", DownloadCount & " 次下载,")
-            'End If
-            Info += GetTimeSpanString([Date] - Date.Now) & "更新"
-            Info += If(ReleaseType <> 1, "," & ReleaseTypeString, "")
-
-            '建立控件
-            Dim NewItem As New MyListItem With {
-                .Title = DisplayName, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Me,
-                .Info = Info
-            }
-            Select Case ReleaseType
-                Case 1 'R
-                    NewItem.Logo = "pack://application:,,,/images/Icons/R.png"
-                Case 2 'B
-                    NewItem.Logo = "pack://application:,,,/images/Icons/B.png"
-                Case Else 'A
-                    NewItem.Logo = "pack://application:,,,/images/Icons/A.png"
-            End Select
-            AddHandler NewItem.Click, OnClick
-
-            '建立另存为按钮
-            If OnSaveClick IsNot Nothing Then
-                Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"}
-                ToolTipService.SetPlacement(BtnSave, Primitives.PlacementMode.Center)
-                ToolTipService.SetVerticalOffset(BtnSave, 30)
-                ToolTipService.SetHorizontalOffset(BtnSave, 2)
-                AddHandler BtnSave.Click, OnSaveClick
-                NewItem.Buttons = {BtnSave}
-                NewItem.PaddingRight = 35
-            End If
-
-            '结束
-            Return NewItem
-        End Function
-        Public Overrides Function ToString() As String
-            Return DisplayName
-        End Function
-
-    End Class
-
-    ''' <summary>
-    ''' 获取某个 CurseForge 工程下的全部文件列表。
-    ''' 必须在工作线程执行,失败会抛出异常。
-    ''' </summary>
-    Public Function DlCfGetFiles(ProjectId As Integer, IsModPack As Boolean) As List(Of DlCfFile)
-        '获取工程对象
-        Dim TargetProject As DlCfProject
-        If DlCfProjectCache.ContainsKey(ProjectId) Then
-            TargetProject = DlCfProjectCache(ProjectId)
-        Else
-            TargetProject = GetCfProjectFromJson(NetGetCodeByRequestRetry("https://api.curseforge.com/v1/mods/" & ProjectId, IsJson:=True, Encode:=Encoding.UTF8)("data"), IsModPack)
-        End If
-        '获取工程对象的文件列表
-        If TargetProject.Files Is Nothing Then
-            Log("[Download] 开始获取 CurseForge 工程 ID 为 " & ProjectId & " 的文件列表")
-            Dim Json As JArray
-            If TargetProject.IsModPack Then
-                '整合包使用全部文件
-                Json = NetGetCodeByRequestRetry("https://api.curseforge.com/v1/mods/" & ProjectId & "/files?pageSize=999", Accept:="application/json", IsJson:=True)("data")
-            Else
-                'Mod 使用每个版本最新的文件
-                Json = GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(TargetProject.FileIndexes, ",") & "]}", "application/json"))("data")
-            End If
-            Dim Files As New Dictionary(Of Integer, DlCfFile)
-            For Each JsonEntry As JObject In Json
-                Dim NewFile As New DlCfFile(JsonEntry, IsModPack)
-                If NewFile.IsAvailable AndAlso Not Files.ContainsKey(NewFile.FileId) Then Files.Add(NewFile.FileId, NewFile)
-            Next
-            TargetProject.Files = Files.Values.ToList
-        End If
-        '获取前置 Mod 列表
-        If IsModPack Then GoTo ExitSub
-        Dim Deps As New List(Of Integer)
-        For Each File In TargetProject.Files
-            For Each AddonId In File.Dependencies
-                If Not Deps.Contains(AddonId) AndAlso Not DlCfProjectCache.ContainsKey(AddonId) Then Deps.Add(AddonId)
-            Next
-        Next
-        If Deps.Count > 0 Then
-            '获取前置 Mod 工程信息
-            Log("[Download] 文件列表中需要获取的前置 Mod:" & Join(Deps, ","))
-            GetCfProjectListFromJson(GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods", "POST", "{""modIds"": [" & Join(Deps, ",") & "]}", "application/json"))("data"), False)
-        End If
-        '返回
-ExitSub:
-        Return TargetProject.Files
-    End Function
-
-    Public Sub DlCfFilesPreload(Stack As StackPanel, Entrys As List(Of DlCfFile), OnClick As MyListItem.ClickEventHandler)
-        '获取卡片对应的前置 ID
-        '如果为整合包就不会有 Dependencies 信息,所以不用管
-        Dim Deps As New List(Of Integer)
-        For Each File In Entrys
-            For Each AddonId In If(File.Dependencies, New List(Of Integer))
-                If Not Deps.Contains(AddonId) Then
-                    If Not DlCfProjectCache.ContainsKey(AddonId) Then
-                        Log("[Download] 未找到 ID 为 " & AddonId & " 的前置 Mod 信息", LogLevel.Developer)
-                        Continue For
-                    End If
-                    Deps.Add(AddonId)
-                End If
-            Next
-        Next
-        Deps.Remove(306612) '移除 Fabric API
-        If Deps.Count = 0 Then Exit Sub
-        Deps.Sort()
-        '添加开头间隔
-        Stack.Children.Add(New TextBlock With {.Text = "前置 Mod", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 2, 0, 5)})
-        '添加前置 Mod 列表
-        For Each Dep In Deps
-            Dim Item = DlCfProjectCache(Dep).ToCfItem(False, True, AddressOf FrmDownloadMod.ProjectClick)
-            'Item.Margin = New Thickness(5, 0, 0, 0)
-            Stack.Children.Add(Item)
-        Next
-        '添加结尾间隔
-        Stack.Children.Add(New TextBlock With {.Text = "可选版本", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 12, 0, 5)})
-    End Sub
+    Public DlOptiFabricLoader As New LoaderTask(Of Integer, List(Of CompFile))("OptiFabric List Loader",
+        Sub(Task As LoaderTask(Of Integer, List(Of CompFile))) Task.Output = CompFilesGet("322385", True))
 
 
 #End Region
 #End Region
 
 

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

@@ -1457,7 +1457,7 @@ NextVersion:
         Next
         Next
 
 
         '去重
         '去重
-        Dim Result As String = Join(ArrayNoDouble(DeDuplicateDataList), " ")
+        Dim Result As String = Join(DeDuplicateDataList.Distinct.ToList, " ")
 
 
         '添加 MainClass
         '添加 MainClass
         If Version.JsonObject("mainClass") Is Nothing Then
         If Version.JsonObject("mainClass") Is Nothing Then
@@ -1549,7 +1549,7 @@ NextVersion:
             DeDuplicateDataList.Add(CurrentEntry)
             DeDuplicateDataList.Add(CurrentEntry)
         Next
         Next
         '去重
         '去重
-        McLaunchArgumentsGameNew = Join(ArrayNoDouble(DeDuplicateDataList), " ")
+        McLaunchArgumentsGameNew = Join(DeDuplicateDataList.Distinct.ToList, " ")
 
 
         '特别改变 OptiFineTweaker
         '特别改变 OptiFineTweaker
         If (Version.Version.HasForge OrElse Version.Version.HasLiteLoader) AndAlso Version.Version.HasOptiFine Then
         If (Version.Version.HasForge OrElse Version.Version.HasLiteLoader) AndAlso Version.Version.HasOptiFine Then
@@ -1592,7 +1592,7 @@ NextVersion:
         GameArguments.Add("${auth_access_token}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${auth_access_token}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${access_token}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${access_token}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${auth_session}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${auth_session}", McLoginLoader.Output.AccessToken)
-        GameArguments.Add("${user_type}", If(McLoginLoader.Output.Type = "Legacy", "Legacy", "Mojang"))
+        GameArguments.Add("${user_type}", "msa") '#1221
         Dim GameSize As Size = Setup.GetLaunchArgumentWindowSize()
         Dim GameSize As Size = Setup.GetLaunchArgumentWindowSize()
         GameArguments.Add("${resolution_width}", GameSize.Width)
         GameArguments.Add("${resolution_width}", GameSize.Width)
         GameArguments.Add("${resolution_height}", GameSize.Height)
         GameArguments.Add("${resolution_height}", GameSize.Height)
@@ -1982,7 +1982,7 @@ IgnoreCustomSkin:
         End If
         End If
         Dim Paths As New List(Of String)(StartInfo.EnvironmentVariables("Path").Split(";"))
         Dim Paths As New List(Of String)(StartInfo.EnvironmentVariables("Path").Split(";"))
         Paths.Add(McLaunchJavaSelected.PathFolder)
         Paths.Add(McLaunchJavaSelected.PathFolder)
-        StartInfo.EnvironmentVariables("Path") = Join(ArrayNoDouble(Paths), ";")
+        StartInfo.EnvironmentVariables("Path") = Join(Paths.Distinct.ToList, ";")
 
 
         '设置其他参数
         '设置其他参数
         StartInfo.WorkingDirectory = McVersionCurrent.PathIndie
         StartInfo.WorkingDirectory = McVersionCurrent.PathIndie

+ 10 - 14
Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb

@@ -220,7 +220,7 @@ Public Module ModMinecraft
                 PathInEnv = PathInEnv.Trim(" """.ToCharArray())
                 PathInEnv = PathInEnv.Trim(" """.ToCharArray())
                 If Not PathInEnv.EndsWith("\") Then PathInEnv += "\"
                 If Not PathInEnv.EndsWith("\") Then PathInEnv += "\"
                 '粗略检查有效性
                 '粗略检查有效性
-                If File.Exists(PathInEnv & "javaw.exe") Then DictionaryAdd(JavaPreList, PathInEnv, False)
+                If File.Exists(PathInEnv & "javaw.exe") Then JavaPreList(PathInEnv) = False
             Next
             Next
             '查找磁盘中的 Java
             '查找磁盘中的 Java
             For Each Disk As DriveInfo In DriveInfo.GetDrives()
             For Each Disk As DriveInfo In DriveInfo.GetDrives()
@@ -272,7 +272,7 @@ Public Module ModMinecraft
             Try
             Try
                 For Each JavaJsonObject In GetJson(ImportedJava)
                 For Each JavaJsonObject In GetJson(ImportedJava)
                     Dim Entry = JavaEntry.FromJson(JavaJsonObject)
                     Dim Entry = JavaEntry.FromJson(JavaJsonObject)
-                    If Entry.IsUserImport Then DictionaryAdd(JavaPreList, Entry.PathFolder, True)
+                    If Entry.IsUserImport Then JavaPreList(Entry.PathFolder) = True
                 Next
                 Next
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "Java 列表已损坏", LogLevel.Feedback)
                 Log(ex, "Java 列表已损坏", LogLevel.Feedback)
@@ -283,10 +283,7 @@ Public Module ModMinecraft
 
 
             '确保可用并获取详细信息,转入正式列表
             '确保可用并获取详细信息,转入正式列表
             Dim NewJavaList As New List(Of JavaEntry)
             Dim NewJavaList As New List(Of JavaEntry)
-            For Each Entry In ArrayNoDouble(JavaPreList.ToList,
-                                            Function(a As KeyValuePair(Of String, Boolean), b As KeyValuePair(Of String, Boolean))
-                                                Return a.Key.ToLower = b.Key.ToLower '#794
-                                            End Function)
+            For Each Entry In Distinct(JavaPreList.ToList, Function(a, b) a.Key.ToLower = b.Key.ToLower) '#794
                 NewJavaList.Add(New JavaEntry(Entry.Key, Entry.Value))
                 NewJavaList.Add(New JavaEntry(Entry.Key, Entry.Value))
             Next
             Next
             NewJavaList = Sort(JavaCheckList(NewJavaList), AddressOf JavaSorter)
             NewJavaList = Sort(JavaCheckList(NewJavaList), AddressOf JavaSorter)
@@ -373,7 +370,7 @@ Wait:
             Dim Path As String = OriginalPath.FullName.Replace("\\", "\")
             Dim Path As String = OriginalPath.FullName.Replace("\\", "\")
             If Not Path.EndsWith("\") Then Path += "\"
             If Not Path.EndsWith("\") Then Path += "\"
             '若该目录有 Java,则加入结果
             '若该目录有 Java,则加入结果
-            If File.Exists(Path & "javaw.exe") Then DictionaryAdd(Results, Path, Source)
+            If File.Exists(Path & "javaw.exe") Then Results(Path) = Source
             '查找其下的所有文件夹
             '查找其下的所有文件夹
             For Each FolderInfo As DirectoryInfo In OriginalPath.EnumerateDirectories
             For Each FolderInfo As DirectoryInfo In OriginalPath.EnumerateDirectories
                 If FolderInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) Then Continue For '跳过符号链接
                 If FolderInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) Then Continue For '跳过符号链接
@@ -473,7 +470,7 @@ RetryGet:
             Dim UserJava As JavaEntry = Nothing
             Dim UserJava As JavaEntry = Nothing
 
 
             '获取版本独立设置中指定的 Java
             '获取版本独立设置中指定的 Java
-            If RelatedVersion IsNot Nothing AndAlso Setup.Get("VersionArgumentJavaSelect", Version:=RelatedVersion) <> "使用全局设置" Then
+            If RelatedVersion IsNot Nothing AndAlso Setup.Get("VersionArgumentJavaSelect", Version:=RelatedVersion).ToString.StartsWith("{") Then
                 Try
                 Try
                     UserJava = JavaEntry.FromJson(GetJson(Setup.Get("VersionArgumentJavaSelect", Version:=RelatedVersion)))
                     UserJava = JavaEntry.FromJson(GetJson(Setup.Get("VersionArgumentJavaSelect", Version:=RelatedVersion)))
                     '确保版本独立设置中指定的 Java 在 Java 列表中(#978)
                     '确保版本独立设置中指定的 Java 在 Java 列表中(#978)
@@ -533,6 +530,7 @@ RetryGet:
             Else
             Else
                 Select Case MyMsgBox("你在设置中手动指定了使用 Java " & JavaCurrent & ",但当前" & Requirement & "。" & vbCrLf &
                 Select Case MyMsgBox("你在设置中手动指定了使用 Java " & JavaCurrent & ",但当前" & Requirement & "。" & vbCrLf &
                             "如果强制使用该 Java,可能会导致出现异常。" & vbCrLf &
                             "如果强制使用该 Java,可能会导致出现异常。" & vbCrLf &
+                            "你可以将 游戏 Java 设置修改为 自动选择合适的 Java 来避免这一提示。" & vbCrLf &
                             vbCrLf &
                             vbCrLf &
                             " - 指定的 Java:" & UserJava.ToString,
                             " - 指定的 Java:" & UserJava.ToString,
                             "Java 兼容性警告", "让 PCL2 自动选择", "强制使用该 Java", "取消")
                             "Java 兼容性警告", "让 PCL2 自动选择", "强制使用该 Java", "取消")
@@ -853,7 +851,7 @@ NoUserJava:
             _McVersionLast = value
             _McVersionLast = value
             If value Is Nothing Then Exit Property
             If value Is Nothing Then Exit Property
             '重置缓存的 Mod 文件夹
             '重置缓存的 Mod 文件夹
-            PageDownloadCfDetail.CachedFolder = Nothing
+            PageDownloadCompDetail.CachedFolder = Nothing
             '统一通行证重判
             '统一通行证重判
             If AniControlEnabled = 0 AndAlso
             If AniControlEnabled = 0 AndAlso
                Setup.Get("VersionServerNide", Version:=value) <> Setup.Get("CacheNideServer") AndAlso
                Setup.Get("VersionServerNide", Version:=value) <> Setup.Get("CacheNideServer") AndAlso
@@ -2644,12 +2642,10 @@ NextVersion:
                 '普通文件
                 '普通文件
                 Urls.AddRange(DlSourceLibraryGet("https://libraries.minecraft.net" & Token.LocalPath.Replace(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries", "").Replace("\", "/")))
                 Urls.AddRange(DlSourceLibraryGet("https://libraries.minecraft.net" & Token.LocalPath.Replace(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries", "").Replace("\", "/")))
             End If
             End If
-            Result.Add(New NetFile(ArrayNoDouble(Urls.ToArray()), Token.LocalPath, Checker))
+            Result.Add(New NetFile(Urls.Distinct.ToArray, Token.LocalPath, Checker))
         Next
         Next
         '去重并返回
         '去重并返回
-        Return ArrayNoDouble(Result, Function(Left As NetFile, Right As NetFile)
-                                         Return Left.LocalPath = Right.LocalPath
-                                     End Function)
+        Return Distinct(Result, Function(a, b) a.LocalPath = b.LocalPath)
     End Function
     End Function
     ''' <summary>
     ''' <summary>
     ''' 获取对应的支持库文件地址。
     ''' 获取对应的支持库文件地址。
@@ -3268,7 +3264,7 @@ NextVersion:
                             '不知道是个啥玩意儿,直接存储
                             '不知道是个啥玩意儿,直接存储
                             Value = RawValue
                             Value = RawValue
                         End If
                         End If
-                        DictionaryAdd(TomlData.Last.Value, Key, Value)
+                        TomlData.Last.Value(Key) = Value
                     Else
                     Else
                         '不知道是个啥玩意儿
                         '不知道是个啥玩意儿
                         Exit Try
                         Exit Try

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

@@ -5,7 +5,7 @@
     ''' 弹窗要求选择一个整合包文件并进行安装。
     ''' 弹窗要求选择一个整合包文件并进行安装。
     ''' </summary>
     ''' </summary>
     Public Sub ModpackInstall()
     Public Sub ModpackInstall()
-        Dim File As String = SelectFile("压缩包文件(*.rar;*.zip)|*.rar;*.zip", "选择整合包压缩文件") '选择整合包文件
+        Dim File As String = SelectFile("整合包文件(*.rar;*.zip;*.mrpack)|*.rar;*.zip;*.mrpack", "选择整合包压缩文件") '选择整合包文件
         If String.IsNullOrEmpty(File) Then Exit Sub
         If String.IsNullOrEmpty(File) Then Exit Sub
         RunInThread(Sub() ModpackInstall(File))
         RunInThread(Sub() ModpackInstall(File))
     End Sub
     End Sub
@@ -23,6 +23,7 @@
                 Archive = New Compression.ZipArchive(New FileStream(File, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                 Archive = New Compression.ZipArchive(New FileStream(File, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                 '从根目录判断整合包类型
                 '从根目录判断整合包类型
                 If Archive.GetEntry("mcbbs.packmeta") IsNot Nothing Then PackType = 3 : Exit Try 'MCBBS 整合包(优先于 manifest.json 判断)
                 If Archive.GetEntry("mcbbs.packmeta") IsNot Nothing Then PackType = 3 : Exit Try 'MCBBS 整合包(优先于 manifest.json 判断)
+                If Archive.GetEntry("modrinth.index.json") IsNot Nothing Then PackType = 4 : Exit Try 'Modrinth 整合包
                 If Archive.GetEntry("manifest.json") IsNot Nothing Then
                 If Archive.GetEntry("manifest.json") IsNot Nothing Then
                     Dim Json As JObject = GetJson(ReadFile(Archive.GetEntry("manifest.json").Open, Encoding.UTF8))
                     Dim Json As JObject = GetJson(ReadFile(Archive.GetEntry("manifest.json").Open, Encoding.UTF8))
                     If Json("addons") Is Nothing Then
                     If Json("addons") Is Nothing Then
@@ -42,6 +43,7 @@
                     If FullNames.Count <> 2 Then Continue For
                     If FullNames.Count <> 2 Then Continue For
                     '判断是否为关键文件
                     '判断是否为关键文件
                     If FullNames(1) = "mcbbs.packmeta" Then PackType = 3 : Exit Try 'MCBBS 整合包(优先于 manifest.json 判断)
                     If FullNames(1) = "mcbbs.packmeta" Then PackType = 3 : Exit Try 'MCBBS 整合包(优先于 manifest.json 判断)
+                    If FullNames(1) = "modrinth.index.json" Then PackType = 4 : Exit Try 'Modrinth 整合包
                     If FullNames(1) = "manifest.json" Then
                     If FullNames(1) = "manifest.json" Then
                         Dim Json As JObject = GetJson(ReadFile(Entry.Open, Encoding.UTF8))
                         Dim Json As JObject = GetJson(ReadFile(Entry.Open, Encoding.UTF8))
                         If Json("addons") Is Nothing Then
                         If Json("addons") Is Nothing Then
@@ -79,6 +81,9 @@
                 Case 3
                 Case 3
                     Log("[ModPack] 整合包种类:MCBBS")
                     Log("[ModPack] 整合包种类:MCBBS")
                     InstallPackMCBBS(File, Archive, ArchiveBaseFolder)
                     InstallPackMCBBS(File, Archive, ArchiveBaseFolder)
+                Case 4
+                    Log("[ModPack] 整合包种类:Modrinth")
+                    InstallPackModrinth(File, Archive, ArchiveBaseFolder, VersionName, Logo)
                 Case 9
                 Case 9
                     Log("[ModPack] 整合包种类:压缩包")
                     Log("[ModPack] 整合包种类:压缩包")
                     Archive.Dispose()
                     Archive.Dispose()
@@ -152,11 +157,8 @@ Retry:
 #Region "不同类型整合包的安装方法"
 #Region "不同类型整合包的安装方法"
 
 
     'CurseForge
     'CurseForge
-    ''' <summary>
-    ''' 获取安装 CurseForge 整合包的加载器,若失败或跳过则返回 Nothing。
-    ''' 加载器以安装目标版本文件夹为输入。
-    ''' </summary>
-    Private Function InstallPackCurseForgeLoader(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String, VersionName As String, Optional Logo As String = Nothing) As LoaderCombo(Of String)
+    Private Sub InstallPackCurseForge(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String, Optional VersionName As String = Nothing, Optional Logo As String = Nothing)
+
         '读取 Json 文件
         '读取 Json 文件
         Dim Json As JObject
         Dim Json As JObject
         Try
         Try
@@ -164,8 +166,19 @@ Retry:
             If Json("minecraft") Is Nothing OrElse Json("minecraft")("version") Is Nothing Then Throw New Exception("整合包未提供 Minecraft 版本信息")
             If Json("minecraft") Is Nothing OrElse Json("minecraft")("version") Is Nothing Then Throw New Exception("整合包未提供 Minecraft 版本信息")
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "整合包安装信息存在问题", LogLevel.Hint)
             Log(ex, "整合包安装信息存在问题", LogLevel.Hint)
-            Return Nothing
+            Exit Sub
         End Try
         End Try
+
+        '获取版本名
+        Dim ShowRibble As Boolean = VersionName Is Nothing
+        If VersionName Is Nothing Then
+            Dim PackName As String = If(Json("name"), "")
+            Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
+            If Validate.Validate(PackName) <> "" Then PackName = ""
+            VersionName = MyMsgBoxInput("输入版本名称", "", PackName, New ObjectModel.Collection(Of Validate) From {Validate})
+            If String.IsNullOrEmpty(VersionName) Then Exit Sub
+        End If
+
         '获取 Mod API 版本信息
         '获取 Mod API 版本信息
         Dim ForgeVersion As String = Nothing
         Dim ForgeVersion As String = Nothing
         Dim FabricVersion As String = Nothing
         Dim FabricVersion As String = Nothing
@@ -175,7 +188,7 @@ Retry:
                 'Forge 指定
                 'Forge 指定
                 If Id.Contains("recommended") Then
                 If Id.Contains("recommended") Then
                     Log("[ModPack] 该整合包版本过老,已不支持进行安装!", LogLevel.Hint)
                     Log("[ModPack] 该整合包版本过老,已不支持进行安装!", LogLevel.Hint)
-                    Return Nothing
+                    Exit Sub
                 End If
                 End If
                 Try
                 Try
                     Log("[ModPack] 整合包 Forge 版本:" & Id)
                     Log("[ModPack] 整合包 Forge 版本:" & Id)
@@ -250,8 +263,8 @@ Retry:
                         IsResourcePack = False
                         IsResourcePack = False
                     End If
                     End If
                     '实际的添加
                     '实际的添加
-                    FileList.Add(ModJson("id"), New DlCfFile(ModJson, False).GetDownloadFile(
-                                 PathMcFolder & "versions\" & VersionName & "\" & If(IsResourcePack, "resourcepacks", "mods") & "\", False))
+                    FileList.Add(ModJson("id"), New CompFile(ModJson, CompType.Mod).ToNetFile(
+                                 PathMcFolder & "versions\" & VersionName & "\" & If(IsResourcePack, "resourcepacks", "mods") & "\"))
                     Task.Progress += 1 / (1 + ModFileList.Count)
                     Task.Progress += 1 / (1 + ModFileList.Count)
                 Next
                 Next
                 Task.Output = FileList.Values.ToList
                 Task.Output = FileList.Values.ToList
@@ -259,6 +272,7 @@ Retry:
             '下载 Mod 文件
             '下载 Mod 文件
             InstallLoaders.Add(New LoaderDownload("下载 Mod", New List(Of NetFile)) With {.ProgressWeight = ModFileList.Count * 1.5}) '每个 Mod 需要 1.5s
             InstallLoaders.Add(New LoaderDownload("下载 Mod", New List(Of NetFile)) With {.ProgressWeight = ModFileList.Count * 1.5}) '每个 Mod 需要 1.5s
         End If
         End If
+
         '构造加载器
         '构造加载器
         Dim Request As New McInstallRequest With {
         Dim Request As New McInstallRequest With {
             .TargetVersionName = VersionName,
             .TargetVersionName = VersionName,
@@ -271,7 +285,7 @@ Retry:
             InstallExpectTime += InstallLoader.ProgressWeight
             InstallExpectTime += InstallLoader.ProgressWeight
         Next
         Next
         Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
         Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
-        If MergeLoaders Is Nothing Then Return Nothing
+        If MergeLoaders Is Nothing Then Exit Sub
         Dim MergeExpectTime As Double = 0
         Dim MergeExpectTime As Double = 0
         For Each MergeLoader In MergeLoaders
         For Each MergeLoader In MergeLoaders
             MergeExpectTime += MergeLoader.ProgressWeight
             MergeExpectTime += MergeLoader.ProgressWeight
@@ -293,14 +307,15 @@ Retry:
                                                               File.Copy(Logo, VersionFolder & "PCL\Logo.png", True)
                                                               File.Copy(Logo, VersionFolder & "PCL\Logo.png", True)
                                                               WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png")
                                                               WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png")
                                                               WriteIni(VersionFolder & "PCL\Setup.ini", "LogoCustom", "True")
                                                               WriteIni(VersionFolder & "PCL\Setup.ini", "LogoCustom", "True")
-                                                              Log("[Download] 已设置整合包 Logo:" & Logo)
+                                                              Log("[ModPack] 已设置整合包 Logo:" & Logo)
                                                           End If
                                                           End If
                                                           '删除原始整合包文件
                                                           '删除原始整合包文件
-                                                          Dim Target As String = VersionFolder & "原始整合包.zip"
-                                                          If Not Setup.Get("ToolDownloadKeepModpack") AndAlso File.Exists(Target) Then
-                                                              Log("[Download] 根据设置要求删除原始整合包文件:" & Target)
-                                                              File.Delete(Target)
-                                                          End If
+                                                          For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
+                                                              If Not Setup.Get("ToolDownloadKeepModpack") AndAlso File.Exists(Target) Then
+                                                                  Log("[ModPack] 根据设置要求删除原始整合包文件:" & Target)
+                                                                  File.Delete(Target)
+                                                              End If
+                                                          Next
                                                       End Sub) With {.ProgressWeight = 0.1, .Show = False})
                                                       End Sub) With {.ProgressWeight = 0.1, .Show = False})
 
 
         '重复任务检查
         '重复任务检查
@@ -309,38 +324,166 @@ Retry:
             For i = 0 To LoaderTaskbar.Count - 1
             For i = 0 To LoaderTaskbar.Count - 1
                 If LoaderTaskbar(i).Name = LoaderName Then
                 If LoaderTaskbar(i).Name = LoaderName Then
                     Hint("该整合包正在安装中!", HintType.Critical)
                     Hint("该整合包正在安装中!", HintType.Critical)
-                    Return Nothing
+                    Exit Sub
                 End If
                 End If
             Next
             Next
         End SyncLock
         End SyncLock
 
 
         '启动
         '启动
         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}
-        Return Loader
-    End Function
-    Private Sub InstallPackCurseForge(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String, Optional VersionName As String = Nothing, Optional Logo As String = Nothing)
+        Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
+        LoaderTaskbarAdd(Loader)
+        FrmMain.BtnExtraDownload.ShowRefresh()
+        If ShowRibble Then FrmMain.BtnExtraDownload.Ribble()
 
 
+    End Sub
+
+    'Modrinth
+    Private Sub InstallPackModrinth(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String, Optional VersionName As String = Nothing, Optional Logo As String = Nothing)
+
+        '读取 Json 文件
+        Dim Json As JObject
+        Try
+            Json = GetJson(ReadFile(Archive.GetEntry(ArchiveBaseFolder & "modrinth.index.json").Open))
+            If Json("dependencies") Is Nothing OrElse Json("dependencies")("minecraft") Is Nothing Then Throw New Exception("整合包未提供 Minecraft 版本信息")
+        Catch ex As Exception
+            Log(ex, "整合包安装信息存在问题", LogLevel.Hint)
+            Exit Sub
+        End Try
+        '获取 Mod API 版本信息
+        Dim MinecraftVersion As String = Nothing
+        Dim ForgeVersion As String = Nothing
+        Dim FabricVersion As String = Nothing
+        For Each Entry As JProperty In If(Json("dependencies"), {})
+            Select Case Entry.Name.ToLower
+                Case "minecraft"
+                    MinecraftVersion = Entry.Value.ToString
+                Case "forge" 'eg. 14.23.5.2859
+                    ForgeVersion = Entry.Value.ToString
+                    Log("[ModPack] 整合包 Forge 版本:" & ForgeVersion)
+                Case "fabric-loader" 'eg. 0.14.14
+                    FabricVersion = Entry.Value.ToString
+                    Log("[ModPack] 整合包 Fabric 版本:" & FabricVersion)
+                Case "quilt-loader" 'eg. 1.0.0
+                    Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical)
+                    Exit Sub
+                Case Else
+                    Hint($"无法安装整合包,其中出现了未知的 Mod 加载器 {Entry.Value}!", HintType.Critical)
+                    Exit Sub
+            End Select
+        Next
         '获取版本名
         '获取版本名
         Dim ShowRibble As Boolean = VersionName Is Nothing
         Dim ShowRibble As Boolean = VersionName Is Nothing
         If VersionName Is Nothing Then
         If VersionName Is Nothing Then
-            Dim Json As JObject
-            Try
-                Json = GetJson(ReadFile(Archive.GetEntry(ArchiveBaseFolder & "manifest.json").Open))
-                If Json("minecraft") Is Nothing OrElse Json("minecraft")("version") Is Nothing Then Throw New Exception("整合包未提供 Minecraft 版本信息")
-            Catch ex As Exception
-                Log(ex, "整合包安装信息存在问题", LogLevel.Hint)
-                Exit Sub
-            End Try
             Dim PackName As String = If(Json("name"), "")
             Dim PackName As String = If(Json("name"), "")
             Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
             Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
             If Validate.Validate(PackName) <> "" Then PackName = ""
             If Validate.Validate(PackName) <> "" Then PackName = ""
             VersionName = MyMsgBoxInput("输入版本名称", "", PackName, New ObjectModel.Collection(Of Validate) From {Validate})
             VersionName = MyMsgBoxInput("输入版本名称", "", PackName, New ObjectModel.Collection(Of Validate) From {Validate})
             If String.IsNullOrEmpty(VersionName) Then Exit Sub
             If String.IsNullOrEmpty(VersionName) Then Exit Sub
         End If
         End If
+        '解压和配置文件
+        Dim InstallTemp As String = PathTemp & "PackInstall\" & RandomInteger(0, 100000) & "\"
+        Dim InstallLoaders As New List(Of LoaderBase)
+        InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
+            Sub(Task As LoaderTask(Of String, Integer))
+                UnpackFiles(InstallTemp, FileAddress)
+                Task.Progress = 0.5
+                '复制 overrides 文件夹和 client-overrides 文件夹
+                If Directory.Exists(InstallTemp & ArchiveBaseFolder & "overrides") Then
+                    CopyDirectory(InstallTemp & ArchiveBaseFolder & "overrides", PathMcFolder & "versions\" & VersionName)
+                Else
+                    Log("[ModPack] 整合包中未找到 override 目录,已跳过")
+                End If
+                Task.Progress = 0.8
+                If Directory.Exists(InstallTemp & ArchiveBaseFolder & "client-overrides") Then
+                    CopyDirectory(InstallTemp & ArchiveBaseFolder & "client-overrides", PathMcFolder & "versions\" & VersionName)
+                End If
+                Task.Progress = 0.9
+                '开启版本隔离
+                WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
+            End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
+        '获取下载文件列表
+        Dim FileList As New List(Of NetFile)
+        For Each File In If(Json("files"), {})
+            '检查是否需要该文件
+            If File("env") IsNot Nothing Then
+                Select Case File("env")("client").ToString
+                    Case "optional"
+                        If MyMsgBox("是否要下载整合包中的可选文件 " & GetFileNameFromPath(File("path").ToString) & "?",
+                                    "下载可选文件", "是", "否") = 2 Then
+                            Continue For
+                        End If
+                    Case "unsupported"
+                        Continue For
+                End Select
+            End If
+            '添加下载文件
+            FileList.Add(New NetFile(File("downloads").Select(Function(t) t.ToString).ToArray,
+                                     PathMcFolder & "versions\" & VersionName & "\" & File("path").ToString,
+                                     New FileChecker(ActualSize:=File("fileSize").ToObject(Of Long), Hash:=File("hashes")("sha1").ToString)))
+        Next
+        If FileList.Count > 0 Then
+            InstallLoaders.Add(New LoaderDownload("下载额外文件", FileList) With {.ProgressWeight = FileList.Count * 1.5}) '每个 Mod 需要 1.5s
+        End If
 
 
-        '启动加载器
-        Dim Loader = InstallPackCurseForgeLoader(FileAddress, Archive, ArchiveBaseFolder, VersionName, Logo)
-        If Loader Is Nothing Then Exit Sub
+        '构造加载器
+        Dim Request As New McInstallRequest With {
+            .TargetVersionName = VersionName,
+            .MinecraftName = MinecraftVersion,
+            .ForgeVersion = ForgeVersion,
+            .FabricVersion = FabricVersion
+        }
+        Dim InstallExpectTime As Double = 0
+        For Each InstallLoader In InstallLoaders
+            InstallExpectTime += InstallLoader.ProgressWeight
+        Next
+        Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
+        If MergeLoaders Is Nothing Then Exit Sub
+        Dim MergeExpectTime As Double = 0
+        For Each MergeLoader In MergeLoaders
+            MergeExpectTime += MergeLoader.ProgressWeight
+        Next
+        '构造 Libraries 加载器(为了使得 Mods 下载结束后再构造,这样才会下载 JumpLoader 文件)
+        Dim LoadersLib As New List(Of LoaderBase)
+        LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False})
+        LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
+        '构造总加载器
+        Dim Loaders As New List(Of LoaderBase)
+        Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallExpectTime})
+        Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeExpectTime})
+        Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8})
+        Loaders.Add(New LoaderTask(Of String, String)("最终整理文件",
+                                                      Sub(Task As LoaderTask(Of String, String))
+                                                          '设置图标
+                                                          Dim VersionFolder As String = PathMcFolder & "versions\" & VersionName & "\"
+                                                          If Logo IsNot Nothing AndAlso File.Exists(Logo) Then
+                                                              File.Copy(Logo, VersionFolder & "PCL\Logo.png", True)
+                                                              WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png")
+                                                              WriteIni(VersionFolder & "PCL\Setup.ini", "LogoCustom", "True")
+                                                              Log("[ModPack] 已设置整合包 Logo:" & Logo)
+                                                          End If
+                                                          '删除原始整合包文件
+                                                          For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
+                                                              If Not Setup.Get("ToolDownloadKeepModpack") AndAlso File.Exists(Target) Then
+                                                                  Log("[ModPack] 根据设置要求删除原始整合包文件:" & Target)
+                                                                  File.Delete(Target)
+                                                              End If
+                                                          Next
+                                                      End Sub) With {.ProgressWeight = 0.1, .Show = False})
+
+        '重复任务检查
+        Dim LoaderName As String = "Modrinth 整合包安装:" & VersionName & " "
+        SyncLock LoaderTaskbarLock
+            For i = 0 To LoaderTaskbar.Count - 1
+                If LoaderTaskbar(i).Name = LoaderName Then
+                    Hint("该整合包正在安装中!", HintType.Critical)
+                    Exit Sub
+                End If
+            Next
+        End SyncLock
+
+        '启动
+        Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
         Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
         Loader.Start(PathMcFolder & "versions\" & VersionName & "\")
         LoaderTaskbarAdd(Loader)
         LoaderTaskbarAdd(Loader)
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.ShowRefresh()
@@ -382,7 +525,10 @@ Retry:
             WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
             WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
         End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
         End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
         '构造加载器
         '构造加载器
-        If Json("gameVersion") Is Nothing Then Throw New Exception("整合包未提供游戏版本信息")
+        If Json("gameVersion") Is Nothing Then
+            Hint("该整合包未提供游戏版本信息,无法安装!", HintType.Critical)
+            Exit Sub
+        End If
         Dim Request As New McInstallRequest With {
         Dim Request As New McInstallRequest With {
             .TargetVersionName = VersionName,
             .TargetVersionName = VersionName,
             .MinecraftName = Json("gameVersion").ToString
             .MinecraftName = Json("gameVersion").ToString
@@ -477,7 +623,10 @@ Retry:
             WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
             WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
         End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
         End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
         '构造版本安装请求
         '构造版本安装请求
-        If PackJson("components") Is Nothing Then Throw New Exception("整合包未提供游戏版本信息")
+        If PackJson("components") Is Nothing Then
+            Hint("该整合包未提供游戏版本信息,无法安装!", HintType.Critical)
+            Exit Sub
+        End If
         Dim Request As New McInstallRequest With {.TargetVersionName = VersionName}
         Dim Request As New McInstallRequest With {.TargetVersionName = 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
@@ -564,12 +713,18 @@ Retry:
             WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
             WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
         End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
         End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
         '构造加载器
         '构造加载器
-        If Json("addons") Is Nothing Then Throw New Exception("整合包未提供游戏版本信息")
+        If Json("addons") Is Nothing Then
+            Hint("该整合包未提供游戏版本附加信息,无法安装!", HintType.Critical)
+            Exit Sub
+        End If
         Dim Addons As New Dictionary(Of String, String)
         Dim Addons As New Dictionary(Of String, String)
         For Each Entry In Json("addons")
         For Each Entry In Json("addons")
             Addons.Add(Entry("id"), Entry("version"))
             Addons.Add(Entry("id"), Entry("version"))
         Next
         Next
-        If Not Addons.ContainsKey("game") Then Throw New Exception("整合包未提供游戏版本信息")
+        If Not Addons.ContainsKey("game") Then
+            Hint("该整合包未提供游戏版本信息,无法安装!", HintType.Critical)
+            Exit Sub
+        End If
         Dim Request As New McInstallRequest With {
         Dim Request As New McInstallRequest With {
             .TargetVersionName = VersionName,
             .TargetVersionName = VersionName,
             .MinecraftName = Addons("game"),
             .MinecraftName = Addons("game"),

+ 3 - 3
Plain Craft Launcher 2/Pages/PageDownload/MyCfItem.xaml → Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml

@@ -1,10 +1,10 @@
-<Grid x:Class="MyCfItem"
+<Grid x:Class="MyCompItem"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Name="PanBack" Height="57"
+             x:Name="PanBack" Height="59"
              RenderTransformOrigin="0.5,0.5" Background="{StaticResource ColorBrushSemiTransparent}" SnapsToDevicePixels="True">
              RenderTransformOrigin="0.5,0.5" Background="{StaticResource ColorBrushSemiTransparent}" SnapsToDevicePixels="True">
     <Grid.ColumnDefinitions>
     <Grid.ColumnDefinitions>
-        <ColumnDefinition Width="6" />
+        <ColumnDefinition Width="7" />
         <ColumnDefinition Width="Auto" />
         <ColumnDefinition Width="Auto" />
         <ColumnDefinition Width="9" />
         <ColumnDefinition Width="9" />
         <ColumnDefinition Width="1*"/>
         <ColumnDefinition Width="1*"/>

+ 22 - 14
Plain Craft Launcher 2/Pages/PageDownload/MyCfItem.xaml.vb → Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb

@@ -1,4 +1,4 @@
-Public Class MyCfItem
+Public Class MyCompItem
     Public Uuid As Integer = GetUuid()
     Public Uuid As Integer = GetUuid()
 
 
 #Region "Logo"
 #Region "Logo"
@@ -11,33 +11,36 @@
         Set(value As String)
         Set(value As String)
             If _Logo = value OrElse value Is Nothing Then Exit Property
             If _Logo = value OrElse value Is Nothing Then Exit Property
             _Logo = value
             _Logo = value
-            Dim FileAddress = PathTemp & "CFLogo\" & GetHash(_Logo) & ".png"
+            Dim FileAddress = PathTemp & "CompLogo\" & GetHash(_Logo) & ".png"
             Try
             Try
                 If _Logo.ToLower.StartsWith("http") Then
                 If _Logo.ToLower.StartsWith("http") Then
                     '网络图片
                     '网络图片
                     If File.Exists(FileAddress) Then
                     If File.Exists(FileAddress) Then
                         PathLogo.Source = New MyBitmap(FileAddress)
                         PathLogo.Source = New MyBitmap(FileAddress)
+                    ElseIf _Logo.ToLower.EndsWith(".webp") Then 'Modrinth 林业 Mod 使用了不支持的 WebP 格式 Logo
+                        Log($"[Comp] 发现不支持的 WebP 格式图标,已更改为默认图标:{_Logo}")
+                        PathLogo.Source = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png")
                     Else
                     Else
                         PathLogo.Source = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png")
                         PathLogo.Source = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png")
-                        RunInNewThread(Sub() LogoLoader(FileAddress), "CurseForge Logo Loader " & Uuid & "#", ThreadPriority.BelowNormal)
+                        RunInNewThread(Sub() LogoLoader(FileAddress), "Comp Logo Loader " & Uuid & "#", ThreadPriority.BelowNormal)
                     End If
                     End If
                 Else
                 Else
                     '位图
                     '位图
                     PathLogo.Source = New MyBitmap(_Logo)
                     PathLogo.Source = New MyBitmap(_Logo)
                 End If
                 End If
             Catch ex As IOException
             Catch ex As IOException
-                Log(ex, "加载 CurseForge 工程图标时读取失败(" & FileAddress & ")")
+                Log(ex, "加载资源工程图标时读取失败(" & FileAddress & ")")
             Catch ex As ArgumentException
             Catch ex As ArgumentException
                 '考虑缓存的图片本身可能有误
                 '考虑缓存的图片本身可能有误
-                Log(ex, "可视化 CurseForge 工程图标失败(" & FileAddress & ")")
+                Log(ex, "可视化资源工程图标失败(" & FileAddress & ")")
                 Try
                 Try
                     File.Delete(FileAddress)
                     File.Delete(FileAddress)
-                    Log("[Download] 已清理损坏的 CurseForge 工程图标:" & FileAddress)
+                    Log("[Comp] 已清理损坏的资源工程图标:" & FileAddress)
                 Catch exx As Exception
                 Catch exx As Exception
-                    Log(exx, "清理损坏的 CurseForge 工程图标缓存失败(" & FileAddress & ")", LogLevel.Hint)
+                    Log(exx, "清理损坏的资源工程图标缓存失败(" & FileAddress & ")", LogLevel.Hint)
                 End Try
                 End Try
             Catch ex As Exception
             Catch ex As Exception
-                Log(ex, "加载 CurseForge 工程图标失败(" & value & ")")
+                Log(ex, "加载资源工程图标失败(" & value & ")")
             End Try
             End Try
         End Set
         End Set
     End Property
     End Property
@@ -48,16 +51,20 @@
 RetryStart:
 RetryStart:
         Try
         Try
             NetDownload(_Logo, Address & DownloadEnd)
             NetDownload(_Logo, Address & DownloadEnd)
+            Dim LoadError As Exception = Nothing
             RunInUiWait(Sub()
             RunInUiWait(Sub()
                             Try
                             Try
                                 '在地址更换时取消加载
                                 '在地址更换时取消加载
-                                If Not Address = PathTemp & "CFLogo\" & GetHash(_Logo) & ".png" Then Exit Sub
+                                If Not Address = PathTemp & "CompLogo\" & GetHash(_Logo) & ".png" Then Exit Sub
                                 '在完成正常加载后才保存缓存图片
                                 '在完成正常加载后才保存缓存图片
                                 PathLogo.Source = New MyBitmap(Address & DownloadEnd)
                                 PathLogo.Source = New MyBitmap(Address & DownloadEnd)
                             Catch ex As Exception
                             Catch ex As Exception
-                                Log(ex, "读取 CurseForge 工程图标失败(" & Address & ")", LogLevel.Hint)
+                                Log(ex, "读取资源工程图标失败(" & Address & ")")
+                                File.Delete(Address & DownloadEnd)
+                                LoadError = ex
                             End Try
                             End Try
                         End Sub)
                         End Sub)
+            If LoadError IsNot Nothing Then Throw LoadError
             If File.Exists(Address) Then
             If File.Exists(Address) Then
                 File.Delete(Address & DownloadEnd)
                 File.Delete(Address & DownloadEnd)
             Else
             Else
@@ -68,7 +75,8 @@ RetryStart:
                 Retry = True
                 Retry = True
                 GoTo RetryStart
                 GoTo RetryStart
             Else
             Else
-                Log(ex, "下载 CurseForge 工程图标失败")
+                Log(ex, $"下载资源工程图标失败({_Logo})")
+                RunInUi(Sub() PathLogo.Source = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png"))
             End If
             End If
         End Try
         End Try
     End Sub
     End Sub
@@ -83,7 +91,7 @@ RetryStart:
         If IsMouseDown Then
         If IsMouseDown Then
             RaiseEvent Click(sender, e)
             RaiseEvent Click(sender, e)
             If e.Handled Then Exit Sub
             If e.Handled Then Exit Sub
-            Log("[Control] 按下 CurseForge 工程列表项:" & LabTitle.Text)
+            Log("[Control] 按下资源工程列表项:" & LabTitle.Text)
         End If
         End If
     End Sub
     End Sub
 
 
@@ -172,10 +180,10 @@ RetryStart:
                              AaScaleTransform(RectBack, -0.196, 1,,, True)
                              AaScaleTransform(RectBack, -0.196, 1,,, True)
                          })
                          })
             End If
             End If
-            AniStart(Ani, "CfItem Color " & Uuid)
+            AniStart(Ani, "CompItem Color " & Uuid)
         Else
         Else
             '无动画
             '无动画
-            AniStop("CfItem Color " & Uuid)
+            AniStop("CompItem Color " & Uuid)
             If _RectBack IsNot Nothing Then RectBack.Opacity = 0
             If _RectBack IsNot Nothing Then RectBack.Opacity = 0
         End If
         End If
     End Sub
     End Sub

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

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

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

@@ -690,7 +690,7 @@ Public Module ModDownloadLib
             .Title = Entry.NameDisplay, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60,
             .Title = Entry.NameDisplay, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60,
             .Info = If(Entry.IsPreview, "测试版", "正式版") &
             .Info = If(Entry.IsPreview, "测试版", "正式版") &
                     If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime) &
                     If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime) &
-                    If(Entry.RequiredForgeVersion = "", "", ",推荐 Forge 版本:" & Entry.RequiredForgeVersion),
+                    If(Entry.RequiredForgeVersion Is Nothing, ",不兼容 Forge", If(Entry.RequiredForgeVersion = "", "", ",推荐 Forge 版本:" & Entry.RequiredForgeVersion)),
             .Logo = "pack://application:,,,/images/Blocks/GrassPath.png"
             .Logo = "pack://application:,,,/images/Blocks/GrassPath.png"
         }
         }
         AddHandler NewItem.Click, OnClick
         AddHandler NewItem.Click, OnClick
@@ -1730,22 +1730,22 @@ Public Module ModDownloadLib
         '结束
         '结束
         Return NewItem
         Return NewItem
     End Function
     End Function
-    Public Function FabricApiDownloadListItem(Entry As DlCfFile, OnClick As MyListItem.ClickEventHandler) As MyListItem
+    Public Function FabricApiDownloadListItem(Entry As CompFile, OnClick As MyListItem.ClickEventHandler) As MyListItem
         '建立控件
         '建立控件
         Dim NewItem As New MyListItem With {
         Dim NewItem As New MyListItem With {
             .Title = Entry.DisplayName.Split("]")(1).Replace("Fabric API ", "").Replace(" build ", ".").Split("+").First.Trim, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60,
             .Title = Entry.DisplayName.Split("]")(1).Replace("Fabric API ", "").Replace(" build ", ".").Split("+").First.Trim, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60,
-            .Info = Entry.ReleaseTypeString & ",发布于 " & Entry.Date.ToString("yyyy/MM/dd HH:mm"),
+            .Info = Entry.StatusDescription & ",发布于 " & Entry.ReleaseDate.ToString("yyyy/MM/dd HH:mm"),
             .Logo = "pack://application:,,,/images/Blocks/Fabric.png"
             .Logo = "pack://application:,,,/images/Blocks/Fabric.png"
         }
         }
         AddHandler NewItem.Click, OnClick
         AddHandler NewItem.Click, OnClick
         '结束
         '结束
         Return NewItem
         Return NewItem
     End Function
     End Function
-    Public Function OptiFabricDownloadListItem(Entry As DlCfFile, OnClick As MyListItem.ClickEventHandler) As MyListItem
+    Public Function OptiFabricDownloadListItem(Entry As CompFile, OnClick As MyListItem.ClickEventHandler) As MyListItem
         '建立控件
         '建立控件
         Dim NewItem As New MyListItem With {
         Dim NewItem As New MyListItem With {
             .Title = Entry.DisplayName.ToLower.Replace("optifabric-", "").Replace(".jar", "").Trim.TrimStart("v"), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60,
             .Title = Entry.DisplayName.ToLower.Replace("optifabric-", "").Replace(".jar", "").Trim.TrimStart("v"), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60,
-            .Info = Entry.ReleaseTypeString & ",发布于 " & Entry.Date.ToString("yyyy/MM/dd HH:mm"),
+            .Info = Entry.StatusDescription & ",发布于 " & Entry.ReleaseDate.ToString("yyyy/MM/dd HH:mm"),
             .Logo = "pack://application:,,,/images/Blocks/OptiFabric.png"
             .Logo = "pack://application:,,,/images/Blocks/OptiFabric.png"
         }
         }
         AddHandler NewItem.Click, OnClick
         AddHandler NewItem.Click, OnClick
@@ -1804,12 +1804,12 @@ Public Module ModDownloadLib
         ''' <summary>
         ''' <summary>
         ''' 欲下载的 Fabric API 信息。
         ''' 欲下载的 Fabric API 信息。
         ''' </summary>
         ''' </summary>
-        Public FabricApi As DlCfFile = Nothing
+        Public FabricApi As CompFile = Nothing
 
 
         ''' <summary>
         ''' <summary>
         ''' 欲下载的 OptiFabric 信息。
         ''' 欲下载的 OptiFabric 信息。
         ''' </summary>
         ''' </summary>
-        Public OptiFabric As DlCfFile = Nothing
+        Public OptiFabric As CompFile = Nothing
 
 
         ''' <summary>
         ''' <summary>
         ''' 欲下载的 LiteLoader 详细信息。
         ''' 欲下载的 LiteLoader 详细信息。
@@ -1962,11 +1962,11 @@ Public Module ModDownloadLib
         Dim LoaderList As New List(Of LoaderBase)
         Dim LoaderList As New List(Of LoaderBase)
         'Fabric API
         'Fabric API
         If Request.FabricApi IsNot Nothing Then
         If Request.FabricApi IsNot Nothing Then
-            LoaderList.Add(New LoaderDownload("下载 Fabric API", New List(Of NetFile) From {Request.FabricApi.GetDownloadFile(New McVersion(OutputFolder).GetPathIndie(True) & "mods\", False)}) With {.ProgressWeight = 3, .Block = False})
+            LoaderList.Add(New LoaderDownload("下载 Fabric API", New List(Of NetFile) From {Request.FabricApi.ToNetFile(New McVersion(OutputFolder).GetPathIndie(True) & "mods\")}) With {.ProgressWeight = 3, .Block = False})
         End If
         End If
         'OptiFabric
         'OptiFabric
         If Request.OptiFabric IsNot Nothing Then
         If Request.OptiFabric IsNot Nothing Then
-            LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {Request.OptiFabric.GetDownloadFile(New McVersion(OutputFolder).GetPathIndie(True) & "mods\", False)}) With {.ProgressWeight = 3, .Block = False})
+            LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {Request.OptiFabric.ToNetFile(New McVersion(OutputFolder).GetPathIndie(True) & "mods\")}) With {.ProgressWeight = 3, .Block = False})
         End If
         End If
         '原版
         '原版
         Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.MinecraftName, McDownloadClientLoader(Request.MinecraftName, Request.MinecraftJson, Request.TargetVersionName)) With {.Show = False, .ProgressWeight = 39, .Block = Request.ForgeVersion Is Nothing AndAlso Request.OptiFineEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}
         Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.MinecraftName, McDownloadClientLoader(Request.MinecraftName, Request.MinecraftJson, Request.TargetVersionName)) With {.Show = False, .ProgressWeight = 39, .Block = Request.ForgeVersion Is Nothing AndAlso Request.OptiFineEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}
@@ -2112,7 +2112,7 @@ Public Module ModDownloadLib
                 SplitArguments.Add(RawArguments(i))
                 SplitArguments.Add(RawArguments(i))
             End If
             End If
         Next
         Next
-        Dim RealArguments As String = Join(ArrayNoDouble(SplitArguments), " ")
+        Dim RealArguments As String = Join(SplitArguments.Distinct.ToList, " ")
         '合并
         '合并
         OutputJson = MinecraftJson
         OutputJson = MinecraftJson
         If HasOptiFine Then
         If HasOptiFine Then

+ 4 - 4
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCfDetail.xaml → Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml

@@ -2,7 +2,7 @@
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
              xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-             mc:Ignorable="d" x:Class="PageDownloadCfDetail"
+             mc:Ignorable="d" x:Class="PageDownloadCompDetail"
              d:DesignWidth="778.571" d:DesignHeight="90.857">
              d:DesignWidth="778.571" d:DesignHeight="90.857">
     <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
     <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
         <Grid Margin="25,25,25,10">
         <Grid Margin="25,25,25,10">
@@ -14,9 +14,9 @@
             <local:MyCard Margin="0,0,0,25" x:Name="CardIntro">
             <local:MyCard Margin="0,0,0,25" x:Name="CardIntro">
                 <StackPanel Margin="22,22,22,19" x:Name="PanIntro">
                 <StackPanel Margin="22,22,22,19" x:Name="PanIntro">
                     <StackPanel Orientation="Horizontal" Height="35">
                     <StackPanel Orientation="Horizontal" Height="35">
-                        <local:MyButton x:Name="BtnIntroCf"    MinWidth="140" Text="查看 CurseForge" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                        <local:MyButton x:Name="BtnIntroWiki"  MinWidth="140" Text="查看百科" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
-                        <local:MyButton x:Name="BtnIntroMCBBS" MinWidth="140" Text="查看 MCBBS" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
+                        <local:MyButton x:Name="BtnIntroWeb"   MinWidth="140" Text="转到 CurseForge" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
+                        <local:MyButton x:Name="BtnIntroWiki"  MinWidth="140" Text="转到 MC 百科" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
+                        <local:MyButton x:Name="BtnIntroMCBBS" MinWidth="140" Text="转到 MCBBS" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
                         <local:MyButton x:Name="BtnIntroCopy"  MinWidth="140" Text="复制名称" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
                         <local:MyButton x:Name="BtnIntroCopy"  MinWidth="140" Text="复制名称" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
                     </StackPanel>
                     </StackPanel>
                 </StackPanel>
                 </StackPanel>

+ 91 - 92
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCfDetail.xaml.vb → Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb

@@ -1,25 +1,25 @@
-Public Class PageDownloadCfDetail
-    Private CfItem As MyCfItem = Nothing
-    Private Project As DlCfProject
+Public Class PageDownloadCompDetail
+    Private CompItem As MyCompItem = Nothing
+    Private Project As CompProject
 
 
 #Region "加载器"
 #Region "加载器"
 
 
+    Private CompFileLoader As New LoaderTask(Of Integer, List(Of CompFile))(
+        "Comp File", Sub(Task As LoaderTask(Of Integer, List(Of CompFile))) Task.Output = CompFilesGet(Project.Id, Project.FromCurseForge))
+
     '初始化加载器信息
     '初始化加载器信息
-    Private Sub PageDownloadCfDetail_Inited(sender As Object, e As EventArgs) Handles Me.Initialized
+    Private Sub PageDownloadCompDetail_Inited(sender As Object, e As EventArgs) Handles Me.Initialized
         Project = FrmMain.PageCurrent.Additional
         Project = FrmMain.PageCurrent.Additional
-        PageLoaderInit(Load, PanLoad, PanMain, CardIntro, DlCfFileLoader, AddressOf Load_OnFinish, AddressOf LoaderInput)
+        PageLoaderInit(Load, PanLoad, PanMain, CardIntro, CompFileLoader, AddressOf Load_OnFinish)
     End Sub
     End Sub
-    Private Function LoaderInput() As KeyValuePair(Of Integer, Boolean)
-        Return New KeyValuePair(Of Integer, Boolean)(Project.Id, Project.IsModPack)
-    End Function
     '自动重试
     '自动重试
     Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged
     Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged
-        Select Case DlCfFileLoader.State
+        Select Case CompFileLoader.State
             Case LoadState.Failed
             Case LoadState.Failed
                 Dim ErrorMessage As String = ""
                 Dim ErrorMessage As String = ""
-                If DlCfFileLoader.Error IsNot Nothing Then ErrorMessage = DlCfFileLoader.Error.Message
+                If CompFileLoader.Error IsNot Nothing Then ErrorMessage = CompFileLoader.Error.Message
                 If ErrorMessage.Contains("不是有效的 Json 文件") Then
                 If ErrorMessage.Contains("不是有效的 Json 文件") Then
-                    Log("[Download] 下载的 Mod 列表 Json 文件损坏,已自动重试", LogLevel.Debug)
+                    Log("[Comp] 下载的文件 Json 列表损坏,已自动重试", LogLevel.Debug)
                     PageLoaderRestart()
                     PageLoaderRestart()
                 End If
                 End If
         End Select
         End Select
@@ -42,38 +42,42 @@
 
 
         '初始化字典,并将当前版本排在最上面
         '初始化字典,并将当前版本排在最上面
         Dim TopVersion As String
         Dim TopVersion As String
-        If Project.IsModPack Then
-            TopVersion = If(PageDownloadPack.Loader.Input.GameVersion, "")
-        Else
-            TopVersion = If(PageDownloadMod.Loader.Input.GameVersion, "")
-        End If
-        Dim Dict As New SortedDictionary(Of String, List(Of DlCfFile))(New VersionSorterWithSelect(TopVersion))
-        'PCL#8826 汇报了奇怪的这个 Try 块里出现 NPE 的问题,但实在找不到,就瞎加点检测
+        Select Case Project.Type
+            Case CompType.Mod
+                TopVersion = If(PageDownloadMod.Loader.Input.GameVersion, "")
+            Case CompType.ModPack
+                TopVersion = If(PageDownloadPack.Loader.Input.GameVersion, "")
+            Case Else 'CompType.ResourcePack
+                'FUTURE: Res
+                TopVersion = Nothing 'If(PageDownloadResource.Loader.Input.GameVersion, "")
+        End Select
+        Dim Dict As New SortedDictionary(Of String, List(Of CompFile))(New VersionSorterWithSelect(TopVersion))
+        '#8826 汇报了奇怪的这个 Try 块里出现 NPE 的问题,但实在找不到,就瞎加点检测
         Try
         Try
-            Dict.Add("未知版本", New List(Of DlCfFile))
-            If DlCfFileLoader.Output Is Nothing Then
-                Log("[Download] 列表加载输出为 Nothing,请反馈此问题", LogLevel.Feedback)
+            Dict.Add("未知版本", New List(Of CompFile))
+            If CompFileLoader.Output Is Nothing Then
+                Log("[Comp] 列表加载输出为 Nothing,请反馈此问题", LogLevel.Feedback)
                 Exit Try
                 Exit Try
             End If
             End If
-            For Each Version As DlCfFile In DlCfFileLoader.Output
+            For Each Version As CompFile In CompFileLoader.Output
                 If Version Is Nothing Then
                 If Version Is Nothing Then
-                    Log("[Download] 列表中的一个版本为 Nothing,请反馈此问题", LogLevel.Feedback)
+                    Log("[Comp] 列表中的一个版本为 Nothing,请反馈此问题", LogLevel.Feedback)
                     Continue For
                     Continue For
                 End If
                 End If
-                If Version.GameVersion Is Nothing Then
-                    Log("[Download] 列表中的一个版本没有任何适配的游戏版本,返回为 Nothing,请反馈此问题", LogLevel.Feedback)
-                    Continue For
-                End If
-                For Each GameVersion In Version.GameVersion
+                For Each GameVersion In Version.GameVersions
                     '决定添加到哪个版本
                     '决定添加到哪个版本
                     Dim TargetVersion As String
                     Dim TargetVersion As String
-                    If GameVersion Is Nothing OrElse GameVersion.Split(".").Count < 2 Then
+                    If GameVersion Is Nothing Then
                         TargetVersion = "未知版本"
                         TargetVersion = "未知版本"
-                    Else
+                    ElseIf GameVersion.Contains("w") Then
+                        TargetVersion = "快照版本"
+                    ElseIf GameVersion.Split(".").Count >= 2 Then
                         TargetVersion = GameVersion
                         TargetVersion = GameVersion
+                    Else
+                        TargetVersion = "未知版本"
                     End If
                     End If
                     '实际进行添加
                     '实际进行添加
-                    If Not Dict.ContainsKey(TargetVersion) Then Dict.Add(TargetVersion, New List(Of DlCfFile))
+                    If Not Dict.ContainsKey(TargetVersion) Then Dict.Add(TargetVersion, New List(Of CompFile))
                     If Not Dict(TargetVersion).Contains(Version) Then Dict(TargetVersion).Add(Version)
                     If Not Dict(TargetVersion).Contains(Version) Then Dict(TargetVersion).Add(Version)
                 Next
                 Next
             Next
             Next
@@ -86,17 +90,17 @@
             '清空当前
             '清空当前
             PanMain.Children.Clear()
             PanMain.Children.Clear()
             '转化为 UI
             '转化为 UI
-            For Each Pair As KeyValuePair(Of String, List(Of DlCfFile)) In Dict
+            For Each Pair As KeyValuePair(Of String, List(Of CompFile)) In Dict
                 If Pair.Value.Count = 0 Then Continue For
                 If Pair.Value.Count = 0 Then Continue For
                 '增加卡片
                 '增加卡片
-                Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15), .SwapType = If(Project.IsModPack, 9, 8)}
+                Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15), .SwapType = If(Project.Type = CompType.ModPack, 9, 8)} 'FUTURE: Res
                 Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
                 Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
                 NewCard.Children.Add(NewStack)
                 NewCard.Children.Add(NewStack)
                 NewCard.SwapControl = NewStack
                 NewCard.SwapControl = NewStack
                 PanMain.Children.Add(NewCard)
                 PanMain.Children.Add(NewCard)
                 '确定卡片是否展开
                 '确定卡片是否展开
                 If Pair.Key = TopVersion Then
                 If Pair.Key = TopVersion Then
-                    MyCard.StackInstall(NewStack, If(Project.IsModPack, 9, 8), Pair.Key)
+                    MyCard.StackInstall(NewStack, If(Project.Type = CompType.ModPack, 9, 8), Pair.Key) 'FUTURE: Res
                 Else
                 Else
                     NewCard.IsSwaped = True
                     NewCard.IsSwaped = True
                 End If
                 End If
@@ -133,14 +137,15 @@
         End If
         End If
 
 
         '放置当前工程
         '放置当前工程
-        If CfItem IsNot Nothing Then PanIntro.Children.Remove(CfItem)
-        CfItem = Project.ToCfItem(True, True)
-        CfItem.Margin = New Thickness(-7, -7, 0, 8)
-        PanIntro.Children.Insert(0, CfItem)
+        If CompItem IsNot Nothing Then PanIntro.Children.Remove(CompItem)
+        CompItem = Project.ToCompItem(True, True)
+        CompItem.Margin = New Thickness(-7, -7, 0, 8)
+        PanIntro.Children.Insert(0, CompItem)
 
 
         '决定按钮显示
         '决定按钮显示
+        BtnIntroWeb.Text = If(Project.FromCurseForge, "转到 CurseForge", "转到 Modrinth")
         BtnIntroWiki.Visibility = If(Project.WikiId = 0, Visibility.Collapsed, Visibility.Visible)
         BtnIntroWiki.Visibility = If(Project.WikiId = 0, Visibility.Collapsed, Visibility.Visible)
-        BtnIntroMCBBS.Visibility = If(Project.MCBBS Is Nothing, Visibility.Collapsed, Visibility.Visible)
+        BtnIntroMCBBS.Visibility = If(Project.MCBBS = 0, Visibility.Collapsed, Visibility.Visible)
 
 
         AniControlEnabled -= 1
         AniControlEnabled -= 1
     End Sub
     End Sub
@@ -150,11 +155,11 @@
         Try
         Try
 
 
             '获取基本信息
             '获取基本信息
-            Dim File As DlCfFile = sender.Tag
-            Dim LoaderName As String = "CurseForge 整合包下载:" & Project.ChineseName & " "
+            Dim File As CompFile = sender.Tag
+            Dim LoaderName As String = $"{If(Project.FromCurseForge, "CurseForge", "Modrinth")} 整合包下载:{Project.TranslatedName} "
 
 
             '获取版本名
             '获取版本名
-            Dim PackName As String = Project.ChineseName.Replace(".zip", "").Replace(".rar", "").Replace("\", "\").Replace("/", "/").Replace("|", "|").Replace(":", ":").Replace("<", "<").Replace(">", ">").Replace("*", "*").Replace("?", "?").Replace("""", "").Replace(": ", ":")
+            Dim PackName As String = Project.TranslatedName.Replace(".zip", "").Replace(".rar", "").Replace(".mrpack", "").Replace("\", "\").Replace("/", "/").Replace("|", "|").Replace(":", ":").Replace("<", "<").Replace(">", ">").Replace("*", "*").Replace("?", "?").Replace("""", "").Replace(": ", ":")
             Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
             Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
             If Validate.Validate(PackName) <> "" Then PackName = ""
             If Validate.Validate(PackName) <> "" Then PackName = ""
             Dim VersionName As String = MyMsgBoxInput("输入版本名称", "", PackName, New ObjectModel.Collection(Of Validate) From {Validate})
             Dim VersionName As String = MyMsgBoxInput("输入版本名称", "", PackName, New ObjectModel.Collection(Of Validate) From {Validate})
@@ -162,15 +167,15 @@
 
 
             '构造步骤加载器
             '构造步骤加载器
             Dim Loaders As New List(Of LoaderBase)
             Dim Loaders As New List(Of LoaderBase)
-            Dim Target As String = PathMcFolder & "versions\" & VersionName & "\原始整合包.zip"
-            Dim LogoFileAddress As String = PathTemp & "CFLogo\" & GetHash(CfItem.Logo) & ".png"
-            Loaders.Add(New LoaderDownload("下载整合包文件", New List(Of NetFile) From {File.GetDownloadFile(Target, True)}) With {.ProgressWeight = 10, .Block = True})
+            Dim Target As String = $"{PathMcFolder}versions\{VersionName}\原始整合包.{If(Project.FromCurseForge, "zip", "mrpack")}"
+            Dim LogoFileAddress As String = PathTemp & "CompLogo\" & GetHash(CompItem.Logo) & ".png"
+            Loaders.Add(New LoaderDownload("下载整合包文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 10, .Block = True})
             Loaders.Add(New LoaderTask(Of Integer, Integer)("准备安装整合包",
             Loaders.Add(New LoaderTask(Of Integer, Integer)("准备安装整合包",
-                                                            Sub()
-                                                                If Not ModpackInstall(Target, VersionName, Logo:=If(IO.File.Exists(LogoFileAddress), LogoFileAddress, Nothing)) Then
-                                                                    Throw New Exception("整合包安装出现异常!")
-                                                                End If
-                                                            End Sub) With {.ProgressWeight = 0.1})
+                Sub()
+                    If Not ModpackInstall(Target, VersionName, Logo:=If(IO.File.Exists(LogoFileAddress), LogoFileAddress, Nothing)) Then
+                        Throw New Exception("整合包安装出现异常!")
+                    End If
+                End Sub) With {.ProgressWeight = 0.1})
 
 
             '启动
             '启动
             Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = Sub(MyLoader)
             Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = Sub(MyLoader)
@@ -190,60 +195,54 @@
             FrmMain.BtnExtraDownload.Ribble()
             FrmMain.BtnExtraDownload.Ribble()
 
 
         Catch ex As Exception
         Catch ex As Exception
-            Log(ex, "下载 CurseForge 整合包失败", LogLevel.Feedback)
+            Log(ex, "下载资源整合包失败", LogLevel.Feedback)
         End Try
         End Try
     End Sub
     End Sub
     'Mod 下载;整合包另存为
     'Mod 下载;整合包另存为
     Public Shared CachedFolder As String = Nothing '仅在本次缓存的下载文件夹
     Public Shared CachedFolder As String = Nothing '仅在本次缓存的下载文件夹
     Public Sub Save_Click(sender As Object, e As EventArgs)
     Public Sub Save_Click(sender As Object, e As EventArgs)
-        Dim File As DlCfFile = If(TypeOf sender Is MyListItem, sender, sender.Parent).Tag
+        Dim File As CompFile = If(TypeOf sender Is MyListItem, sender, sender.Parent).Tag
         RunInNewThread(
         RunInNewThread(
         Sub()
         Sub()
             Try
             Try
-                Dim Desc As String = If(Project.IsModPack, "整合包", "Mod ")
+                Dim Desc As String = If(Project.Type = CompType.ModPack, "整合包", If(Project.Type = CompType.Mod, "Mod ", "资源包"))
                 '确认默认保存位置
                 '确认默认保存位置
                 Dim DefaultFolder As String = Nothing
                 Dim DefaultFolder As String = Nothing
-                If Not Project.IsModPack Then
+                If Project.Type = CompType.Mod Then
                     '获取 Mod 所需的加载器种类
                     '获取 Mod 所需的加载器种类
                     Dim AllowForge As Boolean? = Nothing, AllowFabric As Boolean? = Nothing
                     Dim AllowForge As Boolean? = Nothing, AllowFabric As Boolean? = Nothing
                     If File.ModLoaders.Count > 0 Then '从文件中获取
                     If File.ModLoaders.Count > 0 Then '从文件中获取
-                        AllowForge = False : AllowFabric = False
-                        For Each LoaderType In File.ModLoaders
-                            If LoaderType = "Forge" Then AllowForge = True
-                            If LoaderType = "Fabric" Then AllowFabric = True
-                        Next
+                        AllowForge = File.ModLoaders.Contains(CompModLoaderType.Forge)
+                        AllowFabric = File.ModLoaders.Contains(CompModLoaderType.Fabric)
                     ElseIf Project.ModLoaders.Count > 0 Then '从工程中获取
                     ElseIf Project.ModLoaders.Count > 0 Then '从工程中获取
-                        AllowForge = False : AllowFabric = False
-                        For Each LoaderType In Project.ModLoaders
-                            If LoaderType = "Forge" Then AllowForge = True
-                            If LoaderType = "Fabric" Then AllowFabric = True
-                        Next
+                        AllowForge = Project.ModLoaders.Contains(CompModLoaderType.Forge)
+                        AllowFabric = Project.ModLoaders.Contains(CompModLoaderType.Fabric)
                     End If
                     End If
                     If AllowForge IsNot Nothing AndAlso Not AllowForge AndAlso
                     If AllowForge IsNot Nothing AndAlso Not AllowForge AndAlso
                        AllowFabric IsNot Nothing AndAlso Not AllowFabric Then
                        AllowFabric IsNot Nothing AndAlso Not AllowFabric Then
                         AllowForge = Nothing : AllowFabric = Nothing
                         AllowForge = Nothing : AllowFabric = Nothing
                     End If
                     End If
-                    Log("[Download] 允许 Forge:" & If(AllowForge, "未知") & ",允许 Fabric:" & If(AllowFabric, "未知"))
+                    Log("[Comp] 允许 Forge:" & If(AllowForge, "未知") & ",允许 Fabric:" & If(AllowFabric, "未知"))
                     '判断某个版本是否符合要求
                     '判断某个版本是否符合要求
                     Dim IsVersionSuitable As Func(Of McVersion, Boolean) =
                     Dim IsVersionSuitable As Func(Of McVersion, Boolean) =
-                            Function(Version)
-                                If Not Version.IsLoaded Then Version.Load()
-                                If Not Version.Modable Then Return False
-                                If File.GameVersion.Any(Function(v) v.Contains(".")) AndAlso
-                                   Not File.GameVersion.Any(Function(v) v.Contains(".") AndAlso v.Split(".")(1) = Version.Version.McCodeMain.ToString) Then Return False
-                                If AllowForge Is Nothing OrElse AllowFabric Is Nothing Then Return True
-                                If AllowForge AndAlso Version.Version.HasForge Then Return True
-                                If AllowFabric AndAlso Version.Version.HasFabric Then Return True
-                                Return False
-                            End Function
+                    Function(Version)
+                        If Not Version.IsLoaded Then Version.Load()
+                        If Not Version.Modable Then Return False
+                        If File.GameVersions.Any(Function(v) v.Contains(".")) AndAlso
+                           Not File.GameVersions.Any(Function(v) v.Contains(".") AndAlso v.Split(".")(1) = Version.Version.McCodeMain.ToString) Then Return False
+                        If AllowForge Is Nothing OrElse AllowFabric Is Nothing Then Return True
+                        If AllowForge AndAlso Version.Version.HasForge Then Return True
+                        If AllowFabric AndAlso Version.Version.HasFabric Then Return True
+                        Return False
+                    End Function
                     '获取 Mod 默认下载位置
                     '获取 Mod 默认下载位置
                     If CachedFolder IsNot Nothing Then
                     If CachedFolder IsNot Nothing Then
                         DefaultFolder = CachedFolder
                         DefaultFolder = CachedFolder
-                        Log("[Download] 使用上次下载时的文件夹作为默认下载位置")
+                        Log("[Comp] 使用上次下载时的文件夹作为默认下载位置")
                     ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then
                     ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then
                         DefaultFolder = McVersionCurrent.PathIndie & "mods\"
                         DefaultFolder = McVersionCurrent.PathIndie & "mods\"
                         Directory.CreateDirectory(DefaultFolder)
                         Directory.CreateDirectory(DefaultFolder)
-                        Log("[Download] 使用当前版本的 mods 文件夹作为默认下载位置(" & McVersionCurrent.Name & ")")
+                        Log("[Comp] 使用当前版本的 mods 文件夹作为默认下载位置(" & McVersionCurrent.Name & ")")
                     Else
                     Else
                         Dim NeedLoad As Boolean = McVersionListLoader.State <> LoadState.Finished
                         Dim NeedLoad As Boolean = McVersionListLoader.State <> LoadState.Finished
                         If NeedLoad Then
                         If NeedLoad Then
@@ -259,7 +258,7 @@
                             If NeedLoad Then
                             If NeedLoad Then
                                 Hint("当前 MC 文件夹中没有找到适合这个 Mod 的版本!")
                                 Hint("当前 MC 文件夹中没有找到适合这个 Mod 的版本!")
                             Else
                             Else
-                                Log("[Download] 由于当前版本不兼容,使用当前的 MC 文件夹作为默认下载位置")
+                                Log("[Comp] 由于当前版本不兼容,使用当前的 MC 文件夹作为默认下载位置")
                             End If
                             End If
                         Else '选择 Mod 数量最多的版本
                         Else '选择 Mod 数量最多的版本
                             Dim SelectedVersion = SuitableVersions.OrderBy(Function(v)
                             Dim SelectedVersion = SuitableVersions.OrderBy(Function(v)
@@ -268,13 +267,13 @@
                                                                            End Function).LastOrDefault()
                                                                            End Function).LastOrDefault()
                             DefaultFolder = SelectedVersion.PathIndie & "mods\"
                             DefaultFolder = SelectedVersion.PathIndie & "mods\"
                             Directory.CreateDirectory(DefaultFolder)
                             Directory.CreateDirectory(DefaultFolder)
-                            Log("[Download] 使用适合的游戏版本作为默认下载位置(" & SelectedVersion.Name & ")")
+                            Log("[Comp] 使用适合的游戏版本作为默认下载位置(" & SelectedVersion.Name & ")")
                         End If
                         End If
                     End If
                     End If
                 End If
                 End If
                 '获取基本信息
                 '获取基本信息
-                Dim ChineseName As String = If(Project.ChineseName = Project.Name, "",
-                        Project.ChineseName.Replace(" (", "Å").Split("Å").First.Replace("\", "\").Replace("/", "/").Replace("|", "|").Replace(":", ":").Replace("<", "<").Replace(">", ">").Replace("*", "*").Replace("?", "?").Replace("""", "").Replace(": ", ":"))
+                Dim ChineseName As String = If(Project.TranslatedName = Project.RawName, "",
+                    Project.TranslatedName.Replace(" (", "Å").Split("Å").First.Replace("\", "\").Replace("/", "/").Replace("|", "|").Replace(":", ":").Replace("<", "<").Replace(">", ">").Replace("*", "*").Replace("?", "?").Replace("""", "").Replace(": ", ":"))
                 Dim FileName As String
                 Dim FileName As String
                 Select Case Setup.Get("ToolDownloadTranslate")
                 Select Case Setup.Get("ToolDownloadTranslate")
                     Case 0
                     Case 0
@@ -290,17 +289,17 @@
                 Sub()
                 Sub()
                     '弹窗要求选择保存位置
                     '弹窗要求选择保存位置
                     Dim Target As String
                     Dim Target As String
-                    If File.FileName.EndsWith(".litemod") Then
-                        Target = SelectAs("选择保存位置", FileName, Desc & "文件|" & If(Project.IsModPack, "*.zip", "*.litemod"), DefaultFolder)
-                    Else
-                        Target = SelectAs("选择保存位置", FileName, Desc & "文件|" & If(Project.IsModPack, "*.zip", "*.jar"), DefaultFolder)
-                    End If
+                    Target = SelectAs("选择保存位置", FileName,
+                                      Desc & "文件|" &
+                                      If(Project.Type = CompType.Mod,
+                                          If(File.FileName.EndsWith(".litemod"), "*.litemod", "*.jar"),
+                                          If(File.FileName.EndsWith(".mrpack"), "*.mrpack", "*.zip")), DefaultFolder)
                     If Not Target.Contains("\") Then Exit Sub
                     If Not Target.Contains("\") Then Exit Sub
                     '构造步骤加载器
                     '构造步骤加载器
                     Dim LoaderName As String = Desc & "下载:" & File.DisplayName & " "
                     Dim LoaderName As String = Desc & "下载:" & File.DisplayName & " "
-                    If Target <> DefaultFolder AndAlso Not Project.IsModPack Then CachedFolder = GetPathFromFullPath(Target)
+                    If Target <> DefaultFolder AndAlso Project.Type = CompType.Mod Then CachedFolder = GetPathFromFullPath(Target)
                     Dim Loaders As New List(Of LoaderBase)
                     Dim Loaders As New List(Of LoaderBase)
-                    Loaders.Add(New LoaderDownload("下载文件", New List(Of NetFile) From {File.GetDownloadFile(Target, True)}) With {.ProgressWeight = 6, .Block = True})
+                    Loaders.Add(New LoaderDownload("下载文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 6, .Block = True})
                     '启动
                     '启动
                     Dim Loader As New LoaderCombo(Of Integer)(LoaderName, Loaders) With {.OnStateChanged = AddressOf DownloadStateSave}
                     Dim Loader As New LoaderCombo(Of Integer)(LoaderName, Loaders) With {.OnStateChanged = AddressOf DownloadStateSave}
                     Loader.Start(1)
                     Loader.Start(1)
@@ -309,12 +308,12 @@
                     FrmMain.BtnExtraDownload.Ribble()
                     FrmMain.BtnExtraDownload.Ribble()
                 End Sub)
                 End Sub)
             Catch ex As Exception
             Catch ex As Exception
-                Log(ex, "保存 CurseForge 文件失败", LogLevel.Feedback)
+                Log(ex, "保存资源文件失败", LogLevel.Feedback)
             End Try
             End Try
-        End Sub, "Download CFDetail Save")
+        End Sub, "Download CompDetail Save")
     End Sub
     End Sub
 
 
-    Private Sub BtnIntroCf_Click(sender As Object, e As EventArgs) Handles BtnIntroCf.Click
+    Private Sub BtnIntroWeb_Click(sender As Object, e As EventArgs) Handles BtnIntroWeb.Click
         OpenWebsite(Project.Website)
         OpenWebsite(Project.Website)
     End Sub
     End Sub
     Private Sub BtnIntroWiki_Click(sender As Object, e As EventArgs) Handles BtnIntroWiki.Click
     Private Sub BtnIntroWiki_Click(sender As Object, e As EventArgs) Handles BtnIntroWiki.Click
@@ -324,7 +323,7 @@
         OpenWebsite("https://www.mcbbs.net/thread-" & Project.MCBBS & "-1-1.html")
         OpenWebsite("https://www.mcbbs.net/thread-" & Project.MCBBS & "-1-1.html")
     End Sub
     End Sub
     Private Sub BtnIntroCopy_Click(sender As Object, e As EventArgs) Handles BtnIntroCopy.Click
     Private Sub BtnIntroCopy_Click(sender As Object, e As EventArgs) Handles BtnIntroCopy.Click
-        ClipboardSet(CfItem.LabTitle.Text)
+        ClipboardSet(CompItem.LabTitle.Text)
     End Sub
     End Sub
 
 
 End Class
 End Class

+ 39 - 24
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb

@@ -232,7 +232,7 @@
     End Sub
     End Sub
 
 
     'FabricApi
     'FabricApi
-    Private SelectedFabricApi As DlCfFile = Nothing
+    Private SelectedFabricApi As CompFile = Nothing
     Private Sub SetFabricApiInfoShow(IsShow As String)
     Private Sub SetFabricApiInfoShow(IsShow As String)
         If PanFabricApiInfo.Tag = IsShow Then Exit Sub
         If PanFabricApiInfo.Tag = IsShow Then Exit Sub
         PanFabricApiInfo.Tag = IsShow
         PanFabricApiInfo.Tag = IsShow
@@ -252,7 +252,7 @@
     End Sub
     End Sub
 
 
     'OptiFabric
     'OptiFabric
-    Private SelectedOptiFabric As DlCfFile = Nothing
+    Private SelectedOptiFabric As CompFile = Nothing
     Private Sub SetOptiFabricInfoShow(IsShow As String)
     Private Sub SetOptiFabricInfoShow(IsShow As String)
         If PanOptiFabricInfo.Tag = IsShow Then Exit Sub
         If PanOptiFabricInfo.Tag = IsShow Then Exit Sub
         PanOptiFabricInfo.Tag = IsShow
         PanOptiFabricInfo.Tag = IsShow
@@ -591,18 +591,23 @@
         End If
         End If
         '检查最低 Forge 版本
         '检查最低 Forge 版本
         Dim MinimalForgeVersion As String = "9999.9999"
         Dim MinimalForgeVersion As String = "9999.9999"
+        Dim NotSuitForForge As Boolean = False
         For Each OptiFineVersion As DlOptiFineListEntry In DlOptiFineListLoader.Output.Value
         For Each OptiFineVersion As DlOptiFineListEntry In DlOptiFineListLoader.Output.Value
             If Not OptiFineVersion.NameDisplay.StartsWith(SelectedMinecraftId & " ") Then Continue For '不是同一个大版本
             If Not OptiFineVersion.NameDisplay.StartsWith(SelectedMinecraftId & " ") Then Continue For '不是同一个大版本
-            If SelectedForge Is Nothing OrElse IsOptiFineSuitForForge(OptiFineVersion, SelectedForge) Then
-                Return Nothing
+            If SelectedForge Is Nothing Then Return Nothing '该版本可用
+            If IsOptiFineSuitForForge(OptiFineVersion, SelectedForge) Then
+                Return Nothing '该版本可用
             Else
             Else
-                '设置用于显示的最低 Forge 版本
-                MinimalForgeVersion = If(VersionSortBoolean(MinimalForgeVersion, OptiFineVersion.RequiredForgeVersion),
-                                         OptiFineVersion.RequiredForgeVersion, MinimalForgeVersion)
+                NotSuitForForge = True
+                If OptiFineVersion.RequiredForgeVersion IsNot Nothing Then
+                    '设置用于显示的最低允许的 Forge 版本
+                    MinimalForgeVersion = If(VersionSortBoolean(MinimalForgeVersion, OptiFineVersion.RequiredForgeVersion),
+                                          OptiFineVersion.RequiredForgeVersion, MinimalForgeVersion)
+                End If
             End If
             End If
         Next
         Next
         If MinimalForgeVersion = "9999.9999" Then
         If MinimalForgeVersion = "9999.9999" Then
-            Return "没有可用版本"
+            Return If(NotSuitForForge, "与 Forge 不兼容", "没有可用版本")
         Else
         Else
             Return "需要 Forge " & If(MinimalForgeVersion.Contains("."), "", "#") & MinimalForgeVersion & " 或更高版本"
             Return "需要 Forge " & If(MinimalForgeVersion.Contains("."), "", "#") & MinimalForgeVersion & " 或更高版本"
         End If
         End If
@@ -611,6 +616,7 @@
     '检查某个 OptiFine 是否与某个 Forge 兼容(最低 Forge 版本是否达到需求)
     '检查某个 OptiFine 是否与某个 Forge 兼容(最低 Forge 版本是否达到需求)
     Private Function IsOptiFineSuitForForge(OptiFine As DlOptiFineListEntry, Forge As DlForgeVersionEntry)
     Private Function IsOptiFineSuitForForge(OptiFine As DlOptiFineListEntry, Forge As DlForgeVersionEntry)
         If Forge.Inherit <> OptiFine.Inherit Then Return False '不是同一个大版本
         If Forge.Inherit <> OptiFine.Inherit Then Return False '不是同一个大版本
+        If OptiFine.RequiredForgeVersion Is Nothing Then Return False '不兼容 Forge
         Return (OptiFine.RequiredForgeVersion.Contains(".") AndAlso 'XX.X.XXX
         Return (OptiFine.RequiredForgeVersion.Contains(".") AndAlso 'XX.X.XXX
                 VersionSortInteger(Forge.Version, OptiFine.RequiredForgeVersion) >= 0) OrElse
                 VersionSortInteger(Forge.Version, OptiFine.RequiredForgeVersion) >= 0) OrElse
                (Not OptiFine.RequiredForgeVersion.Contains(".") AndAlso '#XXXX
                (Not OptiFine.RequiredForgeVersion.Contains(".") AndAlso '#XXXX
@@ -749,13 +755,21 @@
             End If
             End If
         End If
         End If
         If Loader.State <> LoadState.Finished Then Return "获取版本列表失败:未知错误,状态为 " & GetStringFromEnum(Loader.State)
         If Loader.State <> LoadState.Finished Then Return "获取版本列表失败:未知错误,状态为 " & GetStringFromEnum(Loader.State)
+        Dim NotSuitForOptiFine As Boolean = False
         For Each Version In Loader.Output
         For Each Version In Loader.Output
             If Version.Category = "universal" OrElse Version.Category = "client" Then Continue For '跳过无法自动安装的版本
             If Version.Category = "universal" OrElse Version.Category = "client" Then Continue For '跳过无法自动安装的版本
             If SelectedFabric IsNot Nothing Then Return "与 Fabric 不兼容"
             If SelectedFabric IsNot Nothing Then Return "与 Fabric 不兼容"
-            If SelectedOptiFine IsNot Nothing AndAlso VersionSortInteger(SelectedMinecraftId, "1.13") >= 0 AndAlso VersionSortInteger("1.14.3", SelectedMinecraftId) >= 0 Then Return "与 OptiFine 不兼容"
+            If SelectedOptiFine IsNot Nothing AndAlso
+                VersionSortInteger(SelectedMinecraftId, "1.13") >= 0 AndAlso VersionSortInteger("1.14.3", SelectedMinecraftId) >= 0 Then
+                Return "与 OptiFine 不兼容" '1.13 ~ 1.14.3 OptiFine 检查
+            End If
+            If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, Version) Then
+                NotSuitForOptiFine = True '与 OptiFine 不兼容
+                Continue For
+            End If
             Return Nothing
             Return Nothing
         Next
         Next
-        Return "该版本不支持自动安装"
+        Return If(NotSuitForOptiFine, "与 OptiFine 不兼容", "该版本不支持自动安装")
     End Function
     End Function
 
 
     '限制展开
     '限制展开
@@ -772,18 +786,19 @@
             Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = LoadForge.State
             Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = LoadForge.State
             If SelectedMinecraftId <> Loader.Input Then Exit Sub
             If SelectedMinecraftId <> Loader.Input Then Exit Sub
             If Loader.State <> LoadState.Finished Then Exit Sub
             If Loader.State <> LoadState.Finished Then Exit Sub
-            '可视化
+            '获取要显示的版本
             Dim Versions As New List(Of DlForgeVersionEntry)
             Dim Versions As New List(Of DlForgeVersionEntry)
             Versions.AddRange(Loader.Output) '复制数组,以免 Output 在实例化后变空
             Versions.AddRange(Loader.Output) '复制数组,以免 Output 在实例化后变空
             If Loader.Output.Count = 0 Then Exit Sub
             If Loader.Output.Count = 0 Then Exit Sub
             PanForge.Children.Clear()
             PanForge.Children.Clear()
-            Versions = Sort(Versions, Function(Left As DlForgeVersionEntry, Right As DlForgeVersionEntry) As Boolean
-                                          Return New Version(Left.Version) > New Version(Right.Version)
-                                      End Function)
+            Versions = Sort(Versions, Function(a, b) New Version(a.Version) > New Version(b.Version)).
+                       Where(Function(v)
+                                 If v.Category = "universal" OrElse v.Category = "client" Then Return False '跳过无法自动安装的版本
+                                 If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, v) Then Return False
+                                 Return True
+                             End Function).ToList
             ForgeDownloadListItemPreload(PanForge, Versions, AddressOf Forge_Selected, False)
             ForgeDownloadListItemPreload(PanForge, Versions, AddressOf Forge_Selected, False)
             For Each Version In Versions
             For Each Version In Versions
-                If Version.Category = "universal" OrElse Version.Category = "client" Then Continue For '跳过无法自动安装的版本
-                If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, Version) Then Continue For
                 PanForge.Children.Add(ForgeDownloadListItem(Version, AddressOf Forge_Selected, False))
                 PanForge.Children.Add(ForgeDownloadListItem(Version, AddressOf Forge_Selected, False))
             Next
             Next
         Catch ex As Exception
         Catch ex As Exception
@@ -941,7 +956,7 @@
             If DlFabricApiLoader.State <> LoadState.Finished Then Exit Sub
             If DlFabricApiLoader.State <> LoadState.Finished Then Exit Sub
             If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing Then Exit Sub
             If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing Then Exit Sub
             '获取版本列表
             '获取版本列表
-            Dim Versions As New List(Of DlCfFile)
+            Dim Versions As New List(Of CompFile)
             For Each Version In DlFabricApiLoader.Output
             For Each Version In DlFabricApiLoader.Output
                 If IsSuitableFabricApi(Version.DisplayName, SelectedMinecraftId) Then
                 If IsSuitableFabricApi(Version.DisplayName, SelectedMinecraftId) Then
                     If Not Version.DisplayName.StartsWith("[") Then
                     If Not Version.DisplayName.StartsWith("[") Then
@@ -953,8 +968,8 @@
             Next
             Next
             If Versions.Count = 0 Then Exit Sub
             If Versions.Count = 0 Then Exit Sub
             '排序
             '排序
-            Versions = Sort(Versions, Function(Left As DlCfFile, Right As DlCfFile) As Boolean
-                                          Return Left.Date > Right.Date
+            Versions = Sort(Versions, Function(Left As CompFile, Right As CompFile) As Boolean
+                                          Return Left.ReleaseDate > Right.ReleaseDate
                                       End Function)
                                       End Function)
             '可视化
             '可视化
             PanFabricApi.Children.Clear()
             PanFabricApi.Children.Clear()
@@ -987,10 +1002,10 @@
     ''' <summary>
     ''' <summary>
     ''' 从显示名判断该 Mod 是否与某版本适配。
     ''' 从显示名判断该 Mod 是否与某版本适配。
     ''' </summary>
     ''' </summary>
-    Private Function IsSuitableOptiFabric(ModFile As DlCfFile, MinecraftVersion As String) As Boolean
+    Private Function IsSuitableOptiFabric(ModFile As CompFile, MinecraftVersion As String) As Boolean
         Try
         Try
             If MinecraftVersion Is Nothing Then Return False
             If MinecraftVersion Is Nothing Then Return False
-            Return ModFile.GameVersion.Contains(MinecraftVersion)
+            Return ModFile.GameVersions.Contains(MinecraftVersion)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "判断 OptiFabric 版本适配性出错(" & MinecraftVersion & ")")
             Log(ex, "判断 OptiFabric 版本适配性出错(" & MinecraftVersion & ")")
             Return False
             Return False
@@ -1026,14 +1041,14 @@
             If DlOptiFabricLoader.State <> LoadState.Finished Then Exit Sub
             If DlOptiFabricLoader.State <> LoadState.Finished Then Exit Sub
             If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing OrElse SelectedOptiFine Is Nothing Then Exit Sub
             If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing OrElse SelectedOptiFine Is Nothing Then Exit Sub
             '获取版本列表
             '获取版本列表
-            Dim Versions As New List(Of DlCfFile)
+            Dim Versions As New List(Of CompFile)
             For Each Version In DlOptiFabricLoader.Output
             For Each Version In DlOptiFabricLoader.Output
                 If IsSuitableOptiFabric(Version, SelectedMinecraftId) Then Versions.Add(Version)
                 If IsSuitableOptiFabric(Version, SelectedMinecraftId) Then Versions.Add(Version)
             Next
             Next
             If Versions.Count = 0 Then Exit Sub
             If Versions.Count = 0 Then Exit Sub
             '排序
             '排序
-            Versions = Sort(Versions, Function(Left As DlCfFile, Right As DlCfFile) As Boolean
-                                          Return Left.Date > Right.Date
+            Versions = Sort(Versions, Function(Left As CompFile, Right As CompFile) As Boolean
+                                          Return Left.ReleaseDate > Right.ReleaseDate
                                       End Function)
                                       End Function)
             '可视化
             '可视化
             PanOptiFabric.Children.Clear()
             PanOptiFabric.Children.Clear()

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

@@ -4,7 +4,7 @@
              xmlns:local="clr-namespace:PCL" AnimatedControl="{Binding ElementName=PanItem, Mode=OneWay}">
              xmlns:local="clr-namespace:PCL" AnimatedControl="{Binding ElementName=PanItem, Mode=OneWay}">
     <StackPanel Margin="0,12,0,0" Name="PanItem">
     <StackPanel Margin="0,12,0,0" Name="PanItem">
 
 
-        <TextBlock x:Name="LabGame" Text="游戏组件" Margin="13,6,5,4" Opacity="0.6" FontSize="12" />
+        <TextBlock x:Name="LabGame" Text="Minecraft" Margin="13,6,5,4" Opacity="0.6" FontSize="12" />
         <local:MyListItem x:Name="ItemInstall" Checked="True" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="1" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="自动安装" 
         <local:MyListItem x:Name="ItemInstall" Checked="True" IsScaleAnimationEnabled="False" Type="RadioBox" Tag="1" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="自动安装" 
             LogoScale="0.87" Logo="M1091.291429 0H78.935771C35.34848 0.035109 0.029257 35.354331 0 78.935771v863.331475c0 43.534629 35.401143 78.994286 78.935771 78.994285H1091.291429c43.534629 0 78.994286-35.401143 78.994285-78.994285V78.871406C1170.156983 35.319223 1134.849463 0.064366 1091.291429 0z m-8.835658 87.771429v78.754377H87.771429v-78.760229h994.684342zM87.771429 933.425737V254.232869h994.684342v679.140205H87.771429v0.058515zM724.95104 340.00896l-206.19264 547.605943a43.903269 43.903269 0 0 1-82.154057-31.012572l206.139977-547.547428a43.944229 43.944229 0 0 1 82.20672 30.954057zM369.558674 545.909029l-85.489371 85.489371 85.489371 85.542034a43.885714 43.885714 0 0 1-62.025143 62.083657l-116.554605-116.560457a43.8272 43.8272 0 0 1 0-62.025143l116.560457-116.49024a43.885714 43.885714 0 0 1 62.019291 61.966629z m610.567315-37.566172a43.885714 43.885714 0 0 1 0 62.083657l-116.560458 116.560457a43.768686 43.768686 0 0 1-62.019291 0 43.885714 43.885714 0 0 1 0-62.083657l85.547886-85.547885-85.547886-85.542035a43.897417 43.897417 0 0 1 62.083657-62.083657l116.496092 116.618972z">
             LogoScale="0.87" Logo="M1091.291429 0H78.935771C35.34848 0.035109 0.029257 35.354331 0 78.935771v863.331475c0 43.534629 35.401143 78.994286 78.935771 78.994285H1091.291429c43.534629 0 78.994286-35.401143 78.994285-78.994285V78.871406C1170.156983 35.319223 1134.849463 0.064366 1091.291429 0z m-8.835658 87.771429v78.754377H87.771429v-78.760229h994.684342zM87.771429 933.425737V254.232869h994.684342v679.140205H87.771429v0.058515zM724.95104 340.00896l-206.19264 547.605943a43.903269 43.903269 0 0 1-82.154057-31.012572l206.139977-547.547428a43.944229 43.944229 0 0 1 82.20672 30.954057zM369.558674 545.909029l-85.489371 85.489371 85.489371 85.542034a43.885714 43.885714 0 0 1-62.025143 62.083657l-116.554605-116.560457a43.8272 43.8272 0 0 1 0-62.025143l116.560457-116.49024a43.885714 43.885714 0 0 1 62.019291 61.966629z m610.567315-37.566172a43.885714 43.885714 0 0 1 0 62.083657l-116.560458 116.560457a43.768686 43.768686 0 0 1-62.019291 0 43.885714 43.885714 0 0 1 0-62.083657l85.547886-85.547885-85.547886-85.542035a43.897417 43.897417 0 0 1 62.083657-62.083657l116.496092 116.618972z">
             <local:MyListItem.Buttons>
             <local:MyListItem.Buttons>
@@ -59,7 +59,7 @@
                 </x:Array>
                 </x:Array>
             </local:MyListItem.Buttons>
             </local:MyListItem.Buttons>
         </local:MyListItem>
         </local:MyListItem>
-        <TextBlock Text="CurseForge" Margin="13,18,5,4" Opacity="0.6" FontSize="12" />
+        <TextBlock Text="资源" Margin="13,18,5,4" Opacity="0.6" FontSize="12" />
         <local:MyListItem x:Name="ItemMod" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="10" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="Mod"
         <local:MyListItem x:Name="ItemMod" Type="RadioBox" IsScaleAnimationEnabled="False" Tag="10" PaddingRight="35" Height="36" VerticalAlignment="Top" Title="Mod"
                           LogoScale="0.97" Logo="M789.504 912.896h-195.072a35.328 35.328 0 0 1-34.816-35.328v-77.312a39.936 39.936 0 0 0-40.448-39.936H481.28a40.448 40.448 0 0 0-40.448 39.936v77.312a34.816 34.816 0 0 1-34.816 35.328H204.8a41.984 41.984 0 0 1-40.448-42.496v-200.192a35.328 35.328 0 0 1 35.328-35.328h72.704a39.936 39.936 0 0 0 39.936-39.936v-37.888a39.936 39.936 0 0 0-39.936-40.448H199.68a35.328 35.328 0 0 1-35.328-35.328V287.744A41.984 41.984 0 0 1 204.8 245.76h176.64v-32.768a102.4 102.4 0 0 1 102.4-102.4h33.792a102.4 102.4 0 0 1 102.4 102.4v32.768h170.496a41.984 41.984 0 0 1 41.984 41.984V460.8h28.672a102.4 102.4 0 0 1 102.4 102.4v33.792a102.4 102.4 0 0 1-102.4 102.4h-28.672V870.4a41.984 41.984 0 0 1-43.008 42.496z m-159.744-70.144h131.584V665.6a34.304 34.304 0 0 1 34.816-34.816h64a31.744 31.744 0 0 0 31.744-31.744V563.2a31.744 31.744 0 0 0-31.744-31.744h-64a34.816 34.816 0 0 1-34.816-35.328V316.416h-177.152a35.328 35.328 0 0 1-35.328-35.328V212.992a31.744 31.744 0 0 0-31.744-31.744h-33.792a31.744 31.744 0 0 0-31.744 31.744v68.096a35.328 35.328 0 0 1-34.816 35.328H234.496v130.048h37.888a110.592 110.592 0 0 1 110.08 110.592v37.888a110.592 110.592 0 0 1-110.08 110.592h-37.888v137.216h136.192v-42.496a110.592 110.592 0 0 1 110.592-110.08h37.888a110.592 110.592 0 0 1 110.592 110.08z">
                           LogoScale="0.97" Logo="M789.504 912.896h-195.072a35.328 35.328 0 0 1-34.816-35.328v-77.312a39.936 39.936 0 0 0-40.448-39.936H481.28a40.448 40.448 0 0 0-40.448 39.936v77.312a34.816 34.816 0 0 1-34.816 35.328H204.8a41.984 41.984 0 0 1-40.448-42.496v-200.192a35.328 35.328 0 0 1 35.328-35.328h72.704a39.936 39.936 0 0 0 39.936-39.936v-37.888a39.936 39.936 0 0 0-39.936-40.448H199.68a35.328 35.328 0 0 1-35.328-35.328V287.744A41.984 41.984 0 0 1 204.8 245.76h176.64v-32.768a102.4 102.4 0 0 1 102.4-102.4h33.792a102.4 102.4 0 0 1 102.4 102.4v32.768h170.496a41.984 41.984 0 0 1 41.984 41.984V460.8h28.672a102.4 102.4 0 0 1 102.4 102.4v33.792a102.4 102.4 0 0 1-102.4 102.4h-28.672V870.4a41.984 41.984 0 0 1-43.008 42.496z m-159.744-70.144h131.584V665.6a34.304 34.304 0 0 1 34.816-34.816h64a31.744 31.744 0 0 0 31.744-31.744V563.2a31.744 31.744 0 0 0-31.744-31.744h-64a34.816 34.816 0 0 1-34.816-35.328V316.416h-177.152a35.328 35.328 0 0 1-35.328-35.328V212.992a31.744 31.744 0 0 0-31.744-31.744h-33.792a31.744 31.744 0 0 0-31.744 31.744v68.096a35.328 35.328 0 0 1-34.816 35.328H234.496v130.048h37.888a110.592 110.592 0 0 1 110.08 110.592v37.888a110.592 110.592 0 0 1-110.08 110.592h-37.888v137.216h136.192v-42.496a110.592 110.592 0 0 1 110.592-110.08h37.888a110.592 110.592 0 0 1 110.592 110.08z">
             <local:MyListItem.Buttons>
             <local:MyListItem.Buttons>

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

@@ -97,9 +97,11 @@
                 DlOptiFabricLoader.Start(IsForceRestart:=True)
                 DlOptiFabricLoader.Start(IsForceRestart:=True)
                 ItemInstall.Checked = True
                 ItemInstall.Checked = True
             Case FormMain.PageSubType.DownloadMod
             Case FormMain.PageSubType.DownloadMod
+                PageDownloadMod.Storage = New CompProjectStorage
                 If FrmDownloadMod IsNot Nothing Then FrmDownloadMod.PageLoaderRestart()
                 If FrmDownloadMod IsNot Nothing Then FrmDownloadMod.PageLoaderRestart()
                 ItemMod.Checked = True
                 ItemMod.Checked = True
             Case FormMain.PageSubType.DownloadPack
             Case FormMain.PageSubType.DownloadPack
+                PageDownloadPack.Storage = New CompProjectStorage
                 If FrmDownloadPack IsNot Nothing Then FrmDownloadPack.PageLoaderRestart()
                 If FrmDownloadPack IsNot Nothing Then FrmDownloadPack.PageLoaderRestart()
                 ItemPack.Checked = True
                 ItemPack.Checked = True
             Case FormMain.PageSubType.DownloadClient
             Case FormMain.PageSubType.DownloadClient

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

@@ -26,27 +26,32 @@
                         <local:MyTextBox x:Name="TextSearchName" Grid.Column="1" Grid.ColumnSpan="10" />
                         <local:MyTextBox x:Name="TextSearchName" Grid.Column="1" Grid.ColumnSpan="10" />
                         <TextBlock VerticalAlignment="Center" Grid.Row="2" HorizontalAlignment="Left" Text="类型" Margin="0,0,25,0" />
                         <TextBlock VerticalAlignment="Center" Grid.Row="2" HorizontalAlignment="Left" Text="类型" Margin="0,0,25,0" />
                         <local:MyComboBox x:Name="ComboSearchTag" Grid.Column="1" Grid.Row="2" MaxDropDownHeight="320">
                         <local:MyComboBox x:Name="ComboSearchTag" Grid.Column="1" Grid.Row="2" MaxDropDownHeight="320">
-                            <local:MyComboBoxItem Content="全部" Tag="0" IsSelected="True" />
-                            <local:MyComboBoxItem Content="世界生成" Tag="406" />
-                            <local:MyComboBoxItem Content="生物群系" Tag="407" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="天然结构" Tag="409" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="维度" Tag="410" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="生物" Tag="411" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="科技" Tag="412" />
-                            <local:MyComboBoxItem Content="交通与移动" Tag="414" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="管道与物流" Tag="415" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="能源" Tag="417" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="红石" Tag="4558" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="仓储" Tag="420" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="农业" Tag="416" />
-                            <local:MyComboBoxItem Content="食物" Tag="436" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="魔法" Tag="419" />
-                            <local:MyComboBoxItem Content="装备与工具" Tag="434" />
-                            <local:MyComboBoxItem Content="冒险与探索" Tag="422" />
-                            <local:MyComboBoxItem Content="建筑与装饰" Tag="424" />
-                            <local:MyComboBoxItem Content="信息显示" Tag="423" />
-                            <local:MyComboBoxItem Content="支持库" Tag="421" />
-                            <local:MyComboBoxItem Content="服务器" Tag="435" />
+                            <local:MyComboBoxItem Content="全部" Tag="" IsSelected="True" />
+                            <local:MyComboBoxItem Content="世界元素" Tag="406/worldgen" />
+                            <local:MyComboBoxItem Content="生物群系" Tag="407/" Margin="15,0,0,0" />
+                            <local:MyComboBoxItem Content="维度" Tag="410/" Margin="15,0,0,0" />
+                            <local:MyComboBoxItem Content="矿物与资源" Tag="408/" Margin="15,0,0,0" />
+                            <local:MyComboBoxItem Content="天然结构" Tag="409/" Margin="15,0,0,0" />
+                            <local:MyComboBoxItem Content="科技" Tag="412/technology" />
+                            <local:MyComboBoxItem Content="管道与物流" Tag="415/" Margin="15,0,0,0" />
+                            <local:MyComboBoxItem Content="自动化" Tag="4843/" Margin="15,0,0,0" />
+                            <local:MyComboBoxItem Content="能源" Tag="417/" Margin="15,0,0,0" />
+                            <local:MyComboBoxItem Content="红石" Tag="4558/" Margin="15,0,0,0" />
+                            <local:MyComboBoxItem Content="食物与烹饪" Tag="436/food" />
+                            <local:MyComboBoxItem Content="农业" Tag="416/" Margin="15,0,0,0" />
+                            <local:MyComboBoxItem Content="游戏机制" Tag="/game-mechanics" />
+                            <local:MyComboBoxItem Content="运输" Tag="414/transportation" />
+                            <local:MyComboBoxItem Content="仓储" Tag="420/storage" />
+                            <local:MyComboBoxItem Content="魔法" Tag="419/magic" />
+                            <local:MyComboBoxItem Content="冒险" Tag="422/adventure" />
+                            <local:MyComboBoxItem Content="装饰" Tag="424/decoration" />
+                            <local:MyComboBoxItem Content="生物" Tag="411/mobs" />
+                            <local:MyComboBoxItem Content="装备与工具" Tag="434/equipment" />
+                            <local:MyComboBoxItem Content="性能优化" Tag="/optimization" />
+                            <local:MyComboBoxItem Content="信息显示" Tag="423/" />
+                            <local:MyComboBoxItem Content="多人" Tag="435/social" />
+                            <local:MyComboBoxItem Content="改良" Tag="5191/utility" />
+                            <local:MyComboBoxItem Content="支持库" Tag="421/library" />
                         </local:MyComboBox>
                         </local:MyComboBox>
                         <TextBlock VerticalAlignment="Center" Grid.Row="2" Grid.Column="3" HorizontalAlignment="Left" Text="版本" Margin="0,0,25,0" />
                         <TextBlock VerticalAlignment="Center" Grid.Row="2" Grid.Column="3" HorizontalAlignment="Left" Text="版本" Margin="0,0,25,0" />
                         <local:MyComboBox x:Name="TextSearchVersion" Grid.Row="2" Grid.Column="4" Grid.ColumnSpan="2" IsEditable="True" MaxDropDownHeight="320" HintText="全部 (也可自行输入)">
                         <local:MyComboBox x:Name="TextSearchVersion" Grid.Row="2" Grid.Column="4" Grid.ColumnSpan="2" IsEditable="True" MaxDropDownHeight="320" HintText="全部 (也可自行输入)">
@@ -88,9 +93,21 @@
                     </Grid>
                     </Grid>
                 </StackPanel>
                 </StackPanel>
             </local:MyCard>
             </local:MyCard>
-            <local:MyCard x:Name="CardProjects" Title="热门 Mod">
-                <StackPanel x:Name="PanProjects" Margin="20,40,18,20" />
-            </local:MyCard>
+            <StackPanel x:Name="PanContent">
+                <local:MyCard x:Name="CardProjects" UseAnimation="False">
+                    <StackPanel x:Name="PanProjects" Margin="12" />
+                </local:MyCard>
+                <local:MyCard Margin="0,15,0,0" x:Name="CardPages" HorizontalAlignment="Center">
+                    <StackPanel Orientation="Horizontal" Height="23" Margin="10,7">
+                        <local:MyIconButton x:Name="BtnPageLeft" IsEnabled="False" Opacity="0.5"
+                            Logo="M671.968176 911.99957c-12.287381 0-24.576482-4.67206-33.951566-14.047144L286.048434 545.984249c-18.751888-18.719204-18.751888-49.12028 0-67.872168L638.016611 126.111222c18.751888-18.751888 49.12028-18.751888 67.872168 0 18.751888 18.719204 18.751888 49.12028 0 67.872168l-318.016611 318.047574L705.888778 830.047574c18.751888 18.751888 18.751888 49.12028 0 67.872168C696.544658 907.32751 684.255557 911.99957 671.968176 911.99957z" />
+                        <TextBlock x:Name="LabPage" Text="1" 
+                                   Margin="7,0" FontSize="15" VerticalAlignment="Center" Foreground="{DynamicResource ColorBrush3}" />
+                        <local:MyIconButton x:Name="BtnPageRight"
+                            Logo="M761.055557 532.128047c0.512619-0.992555 1.343475-1.823411 1.792447-2.848649 8.800538-18.304636 5.919204-40.703346-9.664077-55.424808L399.935923 139.743798c-19.264507-18.208305-49.631179-17.344765-67.872168 1.888778-18.208305 19.264507-17.375729 49.631179 1.888778 67.872168l316.960409 299.839269L335.199677 813.631716c-19.071845 18.399247-19.648112 48.767639-1.247144 67.872168 9.407768 9.791372 21.984142 14.688778 34.560516 14.688778 12.000108 0 24.000215-4.479398 33.311652-13.439914l350.048434-337.375729c0.672598-0.672598 0.927187-1.599785 1.599785-2.303346 0.512619-0.479935 1.056202-0.832576 1.567101-1.343475C757.759656 538.879828 759.199462 535.391265 761.055557 532.128047z" />
+                    </StackPanel>
+                </local:MyCard>
+            </StackPanel>
             <local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="40,50">
             <local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="40,50">
                 <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>

+ 70 - 26
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb

@@ -1,46 +1,61 @@
 Public Class PageDownloadMod
 Public Class PageDownloadMod
 
 
-    '初始化加载器信息
+    Public Const PageSize = 40
+
+    '加载器信息
+    Public Shared Loader As New LoaderTask(Of CompProjectRequest, Integer)("CompProject Mod", AddressOf CompProjectsGet, AddressOf LoaderInput) With {.ReloadTimeout = 60 * 1000}
+    Public Shared Storage As New CompProjectStorage
+    Public Shared Page As Integer = 0
     Private Sub PageDownloadMod_Inited(sender As Object, e As EventArgs) Handles Me.Initialized
     Private Sub PageDownloadMod_Inited(sender As Object, e As EventArgs) Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, CardProjects, PanAlways, Loader, AddressOf Load_OnFinish, AddressOf LoaderInput)
+        PageLoaderInit(Load, PanLoad, PanContent, PanAlways, Loader, AddressOf Load_OnFinish, AddressOf LoaderInput)
         McVersionHighest = Math.Max(McVersionHighest, Integer.Parse(CType(TextSearchVersion.Items(1), MyComboBoxItem).Content.ToString.Split(".")(1)))
         McVersionHighest = Math.Max(McVersionHighest, Integer.Parse(CType(TextSearchVersion.Items(1), MyComboBoxItem).Content.ToString.Split(".")(1)))
     End Sub
     End Sub
-    Public Shared Loader As New LoaderTask(Of DlCfProjectRequest, DlCfProjectResult)("DlCfProject Mod", AddressOf DlCfProjectSub, AddressOf LoaderInput) With {.ReloadTimeout = 60 * 1000}
-    Private Shared Function LoaderInput() As DlCfProjectRequest
+    Private Shared Function LoaderInput() As CompProjectRequest
+        Dim Request As New CompProjectRequest(CompType.Mod, Storage, (Page + 1) * PageSize)
         If FrmDownloadMod IsNot Nothing AndAlso FrmDownloadMod.IsLoaded Then
         If FrmDownloadMod IsNot Nothing AndAlso FrmDownloadMod.IsLoaded Then
-            Return New DlCfProjectRequest With {
-                .SearchFilter = FrmDownloadMod.TextSearchName.Text,
-                .GameVersion = If(FrmDownloadMod.TextSearchVersion.Text.Contains("."), FrmDownloadMod.TextSearchVersion.Text, Nothing),
-                .CategoryId = Val(FrmDownloadMod.ComboSearchTag.SelectedItem.Tag),
-                .IsModPack = False,
+            With Request
+                .SearchText = FrmDownloadMod.TextSearchName.Text
+                .GameVersion = If(FrmDownloadMod.TextSearchVersion.Text.Contains("."), FrmDownloadMod.TextSearchVersion.Text, Nothing)
+                .Tag = FrmDownloadMod.ComboSearchTag.SelectedItem.Tag
                 .ModLoader = Val(FrmDownloadMod.ComboSearchLoader.SelectedItem.Tag)
                 .ModLoader = Val(FrmDownloadMod.ComboSearchLoader.SelectedItem.Tag)
-            }  '处理列表加载失败后的点击加载器重试
-        Else
-            Return New DlCfProjectRequest With {.IsModPack = False}
+            End With
         End If
         End If
+        Return Request
     End Function
     End Function
+
     '结果 UI 化
     '结果 UI 化
     Private Sub Load_OnFinish()
     Private Sub Load_OnFinish()
         Try
         Try
+            '列表项
             PanProjects.Children.Clear()
             PanProjects.Children.Clear()
-            If Loader.Input.SearchFilter = "" Then
-                CardProjects.Title = "热门 Mod"
-            Else
-                CardProjects.Title = "搜索结果 (" & Loader.Output.Projects.Count & If(Loader.Output.Projects.Count < Loader.Output.RealCount, "+", "") & ")"
-            End If
-            For Each Project In Loader.Output.Projects
-                PanProjects.Children.Add(Project.ToCfItem(Loader.Input.GameVersion Is Nothing, Loader.Input.ModLoader Is Nothing OrElse Loader.Input.ModLoader = 0, AddressOf ProjectClick))
+            For i = Math.Min(Page * PageSize, Storage.Results.Count - 1) To Math.Min((Page + 1) * PageSize - 1, Storage.Results.Count - 1)
+                PanProjects.Children.Add(
+                    Storage.Results(i).ToCompItem(Loader.Input.GameVersion Is Nothing,
+                                                  Loader.Input.ModLoader = CompModLoaderType.Any, AddressOf ProjectClick))
             Next
             Next
+            '页码
+            CardPages.Visibility = If(Storage.Results.Count > 40 OrElse
+                                      Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal,
+                                      Visibility.Visible, Visibility.Collapsed)
+            LabPage.Text = Page + 1
+            BtnPageLeft.IsEnabled = Page > 0
+            BtnPageLeft.Opacity = If(BtnPageLeft.IsEnabled, 1, 0.5)
+            BtnPageRight.IsEnabled = Storage.Results.Count > PageSize * (Page + 1) OrElse
+                                     Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal
+            BtnPageRight.Opacity = If(BtnPageRight.IsEnabled, 1, 0.5)
+            '强制返回顶部
+            PanBack.ScrollToTop()
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "可视化 Mod 列表出错", LogLevel.Feedback)
             Log(ex, "可视化 Mod 列表出错", LogLevel.Feedback)
         End Try
         End Try
     End Sub
     End Sub
+
     '自动重试
     '自动重试
     Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged
     Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged
         Select Case Loader.State
         Select Case Loader.State
             Case LoadState.Failed
             Case LoadState.Failed
                 Dim ErrorMessage As String = ""
                 Dim ErrorMessage As String = ""
-                If DlCfFileLoader.Error IsNot Nothing Then ErrorMessage = DlCfFileLoader.Error.Message
+                If Loader.Error IsNot Nothing Then ErrorMessage = Loader.Error.Message
                 If ErrorMessage.Contains("不是有效的 Json 文件") Then
                 If ErrorMessage.Contains("不是有效的 Json 文件") Then
                     Log("[Download] 下载的 Mod 列表 Json 文件损坏,已自动重试", LogLevel.Debug)
                     Log("[Download] 下载的 Mod 列表 Json 文件损坏,已自动重试", LogLevel.Debug)
                     PageLoaderRestart()
                     PageLoaderRestart()
@@ -50,18 +65,43 @@
 
 
     '进入详情页面
     '进入详情页面
 
 
-    Public Sub ProjectClick(sender As MyCfItem, e As EventArgs)
-        FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CfDetail, .Additional = sender.Tag})
+    Public Sub ProjectClick(sender As MyCompItem, e As EventArgs)
+        FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail, .Additional = sender.Tag})
     End Sub
     End Sub
 
 
-    '搜索
+    '切换页码
 
 
-    Private Sub TextSearchName_KeyUp(sender As Object, e As KeyEventArgs) Handles TextSearchName.KeyUp, TextSearchVersion.KeyUp
-        If e.Key = Key.Enter Then StartSearch()
+    Private Sub BtnPageLeft_Click(sender As Object, e As RoutedEventArgs) Handles BtnPageLeft.Click
+        ChangePage(Page - 1)
+    End Sub
+    Private Sub BtnPageRight_Click(sender As Object, e As RoutedEventArgs) Handles BtnPageRight.Click
+        ChangePage(Page + 1)
+    End Sub
+    Private Sub ChangePage(NewPage As Integer)
+        CardPages.IsEnabled = False
+        Page = NewPage
+        FrmMain.BackToTop()
+        Log($"[Download] Mod 切换到第 {Page + 1} 页")
+        RunInThread(Sub()
+                        Thread.Sleep(100) '等待向上滚的动画结束
+                        RunInUi(Sub() CardPages.IsEnabled = True)
+                        Loader.Start()
+                    End Sub)
     End Sub
     End Sub
-    Private Shared Sub StartSearch() Handles BtnSearchRun.Click
+
+#Region "搜索"
+
+    '搜索按钮
+    Private Sub StartNewSearch() Handles BtnSearchRun.Click
+        Page = 0
+        Storage = New CompProjectStorage
         Loader.Start()
         Loader.Start()
     End Sub
     End Sub
+    Private Sub TextSearchName_KeyUp(sender As Object, e As KeyEventArgs) Handles TextSearchName.KeyUp, TextSearchVersion.KeyUp
+        If e.Key = Key.Enter Then StartNewSearch()
+    End Sub
+
+    '重置按钮
     Private Sub BtnSearchReset_Click(sender As Object, e As EventArgs) Handles BtnSearchReset.Click
     Private Sub BtnSearchReset_Click(sender As Object, e As EventArgs) Handles BtnSearchReset.Click
         TextSearchName.Text = ""
         TextSearchName.Text = ""
         TextSearchVersion.SelectedIndex = 0
         TextSearchVersion.SelectedIndex = 0
@@ -70,6 +110,8 @@
         ComboSearchLoader.SelectedIndex = 0
         ComboSearchLoader.SelectedIndex = 0
         Loader.LastFinishedTime = 0 '要求强制重新开始
         Loader.LastFinishedTime = 0 '要求强制重新开始
     End Sub
     End Sub
+
+    '版本选择
     Private Sub TextSearchVersion_TextChanged(sender As Object, e As TextChangedEventArgs) Handles TextSearchVersion.TextChanged
     Private Sub TextSearchVersion_TextChanged(sender As Object, e As TextChangedEventArgs) Handles TextSearchVersion.TextChanged
         If TextSearchVersion.Text = "" Then
         If TextSearchVersion.Text = "" Then
             ComboSearchLoader.Visibility = Visibility.Collapsed
             ComboSearchLoader.Visibility = Visibility.Collapsed
@@ -81,4 +123,6 @@
         End If
         End If
     End Sub
     End Sub
 
 
+#End Region
+
 End Class
 End Class

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

@@ -25,24 +25,25 @@
                         <local:MyTextBox x:Name="TextSearchName" Grid.Column="1" Grid.ColumnSpan="10" HintText="仅支持使用英文搜索" />
                         <local:MyTextBox x:Name="TextSearchName" Grid.Column="1" Grid.ColumnSpan="10" HintText="仅支持使用英文搜索" />
                         <TextBlock VerticalAlignment="Center" Grid.Row="2" HorizontalAlignment="Left" Text="类型" Margin="0,0,25,0" />
                         <TextBlock VerticalAlignment="Center" Grid.Row="2" HorizontalAlignment="Left" Text="类型" Margin="0,0,25,0" />
                         <local:MyComboBox x:Name="ComboSearchTag" Grid.Column="1" Grid.Row="2" MaxDropDownHeight="320">
                         <local:MyComboBox x:Name="ComboSearchTag" Grid.Column="1" Grid.Row="2" MaxDropDownHeight="320">
-                            <local:MyComboBoxItem Content="全部" Tag="0" IsSelected="True" />
-                            <local:MyComboBoxItem IsHitTestVisible="False" Content="规格" />
-                            <local:MyComboBoxItem Tag="4481" Content="小型整合" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Tag="4482" Content="大型整合" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem IsHitTestVisible="False" Content="特殊" />
-                            <local:MyComboBoxItem Tag="4487" Content="FTB" Margin="15,0,0,0" ToolTip="该整合包由 FTB 官方发布" />
-                            <local:MyComboBoxItem Tag="4480" Content="基于地图" Margin="15,0,0,0" ToolTip="该整合包基于已有的 Minecraft 游戏地图运行" />
-                            <local:MyComboBoxItem Tag="4479" Content="硬核" />
-                            <local:MyComboBoxItem Tag="4474" Content="科幻" />
-                            <local:MyComboBoxItem Tag="4483" Content="战斗" />
-                            <local:MyComboBoxItem Tag="4478" Content="任务" />
-                            <local:MyComboBoxItem Tag="4484" Content="多人" />
-                            <local:MyComboBoxItem Tag="4476" Content="探索" />
-                            <local:MyComboBoxItem Tag="4736" Content="空岛" />
-                            <local:MyComboBoxItem Tag="4475" Content="冒险" />
-                            <local:MyComboBoxItem Tag="4472" Content="科技" />
-                            <local:MyComboBoxItem Tag="4473" Content="魔法" />
-                            <local:MyComboBoxItem Tag="4477" Content="小游戏" />
+                            <local:MyComboBoxItem Tag="" Content="全部" IsSelected="True" />
+                            <local:MyComboBoxItem Tag="4484/" Content="多人" />
+                            <local:MyComboBoxItem Tag="/optimization" Content="性能优化" />
+                            <local:MyComboBoxItem Tag="4479/challenging" Content="硬核" />
+                            <local:MyComboBoxItem Tag="4483/combat" Content="战斗" />
+                            <local:MyComboBoxItem Tag="4478/quests" Content="任务" />
+                            <local:MyComboBoxItem Tag="4472/technology" Content="科技" />
+                            <local:MyComboBoxItem Tag="4473/magic" Content="魔法" />
+                            <local:MyComboBoxItem Tag="4475/adventure" Content="冒险" />
+                            <local:MyComboBoxItem Tag="/kitchen-sink" Content="烹饪" />
+                            <local:MyComboBoxItem Tag="4476/" Content="探索" />
+                            <local:MyComboBoxItem Tag="4477/" Content="小游戏" />
+                            <local:MyComboBoxItem Tag="4471/" Content="科幻" />
+                            <local:MyComboBoxItem Tag="4736/" Content="空岛" />
+                            <local:MyComboBoxItem Tag="5128/" Content="原版改良" />
+                            <local:MyComboBoxItem Tag="4487/" Content="FTB" ToolTip="该整合包由 FTB 官方发布" />
+                            <local:MyComboBoxItem Tag="4480/" Content="基于地图" />
+                            <local:MyComboBoxItem Tag="4481/lightweight" Content="轻量整合" />
+                            <local:MyComboBoxItem Tag="4482/" Content="大型整合" />
                         </local:MyComboBox>
                         </local:MyComboBox>
                         <TextBlock VerticalAlignment="Center" Grid.Row="2" Grid.Column="3" HorizontalAlignment="Left" Text="版本" Margin="0,0,25,0" />
                         <TextBlock VerticalAlignment="Center" Grid.Row="2" Grid.Column="3" HorizontalAlignment="Left" Text="版本" Margin="0,0,25,0" />
                         <local:MyComboBox x:Name="TextSearchVersion" Grid.Row="2" Grid.Column="4" IsEditable="True" MaxDropDownHeight="320" HintText="全部 (也可自行输入)">
                         <local:MyComboBox x:Name="TextSearchVersion" Grid.Row="2" Grid.Column="4" IsEditable="True" MaxDropDownHeight="320" HintText="全部 (也可自行输入)">
@@ -81,9 +82,21 @@
                     </Grid>
                     </Grid>
                 </StackPanel>
                 </StackPanel>
             </local:MyCard>
             </local:MyCard>
-            <local:MyCard x:Name="CardProjects" Title="热门整合包">
-                <StackPanel x:Name="PanProjects" Margin="20,40,18,20" />
-            </local:MyCard>
+            <StackPanel x:Name="PanContent">
+                <local:MyCard x:Name="CardProjects" UseAnimation="False">
+                    <StackPanel x:Name="PanProjects" Margin="12" />
+                </local:MyCard>
+                <local:MyCard Margin="0,15,0,0" x:Name="CardPages" HorizontalAlignment="Center">
+                    <StackPanel Orientation="Horizontal" Height="23" Margin="10,7">
+                        <local:MyIconButton x:Name="BtnPageLeft" IsEnabled="False" Opacity="0.5"
+                            Logo="M671.968176 911.99957c-12.287381 0-24.576482-4.67206-33.951566-14.047144L286.048434 545.984249c-18.751888-18.719204-18.751888-49.12028 0-67.872168L638.016611 126.111222c18.751888-18.751888 49.12028-18.751888 67.872168 0 18.751888 18.719204 18.751888 49.12028 0 67.872168l-318.016611 318.047574L705.888778 830.047574c18.751888 18.751888 18.751888 49.12028 0 67.872168C696.544658 907.32751 684.255557 911.99957 671.968176 911.99957z" />
+                        <TextBlock x:Name="LabPage" Text="1" 
+                                   Margin="7,0" FontSize="15" VerticalAlignment="Center" Foreground="{DynamicResource ColorBrush3}" />
+                        <local:MyIconButton x:Name="BtnPageRight"
+                            Logo="M761.055557 532.128047c0.512619-0.992555 1.343475-1.823411 1.792447-2.848649 8.800538-18.304636 5.919204-40.703346-9.664077-55.424808L399.935923 139.743798c-19.264507-18.208305-49.631179-17.344765-67.872168 1.888778-18.208305 19.264507-17.375729 49.631179 1.888778 67.872168l316.960409 299.839269L335.199677 813.631716c-19.071845 18.399247-19.648112 48.767639-1.247144 67.872168 9.407768 9.791372 21.984142 14.688778 34.560516 14.688778 12.000108 0 24.000215-4.479398 33.311652-13.439914l350.048434-337.375729c0.672598-0.672598 0.927187-1.599785 1.599785-2.303346 0.512619-0.479935 1.056202-0.832576 1.567101-1.343475C757.759656 538.879828 759.199462 535.391265 761.055557 532.128047z" />
+                    </StackPanel>
+                </local:MyCard>
+            </StackPanel>
             <local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="40,50">
             <local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="40,50">
                 <local:MyLoading Text="正在获取整合包列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
                 <local:MyLoading Text="正在获取整合包列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
             </local:MyCard>
             </local:MyCard>

+ 72 - 38
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml.vb

@@ -1,45 +1,59 @@
 Public Class PageDownloadPack
 Public Class PageDownloadPack
 
 
-    '初始化加载器信息
+    Public Const PageSize = 40
+
+    '加载器信息
+    Public Shared Loader As New LoaderTask(Of CompProjectRequest, Integer)("CompProject ModPack", AddressOf CompProjectsGet, AddressOf LoaderInput) With {.ReloadTimeout = 60 * 1000}
+    Public Shared Storage As New CompProjectStorage
+    Public Shared Page As Integer = 0
     Private Sub PageDownloadMod_Inited(sender As Object, e As EventArgs) Handles Me.Initialized
     Private Sub PageDownloadMod_Inited(sender As Object, e As EventArgs) Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, CardProjects, PanAlways, Loader, AddressOf Load_OnFinish, AddressOf LoaderInput)
+        PageLoaderInit(Load, PanLoad, PanContent, PanAlways, Loader, AddressOf Load_OnFinish, AddressOf LoaderInput)
         McVersionHighest = Math.Max(McVersionHighest, Integer.Parse(CType(TextSearchVersion.Items(1), MyComboBoxItem).Content.ToString.Split(".")(1)))
         McVersionHighest = Math.Max(McVersionHighest, Integer.Parse(CType(TextSearchVersion.Items(1), MyComboBoxItem).Content.ToString.Split(".")(1)))
     End Sub
     End Sub
-    Public Shared Loader As New LoaderTask(Of DlCfProjectRequest, DlCfProjectResult)("DlCfProject ModPack", AddressOf DlCfProjectSub, AddressOf LoaderInput) With {.ReloadTimeout = 60 * 1000}
-    Private Shared Function LoaderInput() As DlCfProjectRequest
+    Private Shared Function LoaderInput() As CompProjectRequest
+        Dim Request As New CompProjectRequest(CompType.ModPack, Storage, (Page + 1) * PageSize)
         If FrmDownloadPack IsNot Nothing AndAlso FrmDownloadPack.IsLoaded Then
         If FrmDownloadPack IsNot Nothing AndAlso FrmDownloadPack.IsLoaded Then
-            Return New DlCfProjectRequest With {
-                .SearchFilter = FrmDownloadPack.TextSearchName.Text,
-                .GameVersion = If(FrmDownloadPack.TextSearchVersion.Text.Contains("."), FrmDownloadPack.TextSearchVersion.Text, Nothing),
-                .CategoryId = Val(FrmDownloadPack.ComboSearchTag.SelectedItem.Tag),
-                .IsModPack = True
-            } '处理列表加载失败后的点击加载器重试
-        Else
-            Return New DlCfProjectRequest With {.IsModPack = True}
+            With Request
+                .SearchText = FrmDownloadPack.TextSearchName.Text
+                .GameVersion = If(FrmDownloadPack.TextSearchVersion.Text.Contains("."), FrmDownloadPack.TextSearchVersion.Text, Nothing)
+                .Tag = FrmDownloadPack.ComboSearchTag.SelectedItem.Tag
+            End With
         End If
         End If
+        Return Request
     End Function
     End Function
+
     '结果 UI 化
     '结果 UI 化
     Private Sub Load_OnFinish()
     Private Sub Load_OnFinish()
         Try
         Try
+            '列表项
             PanProjects.Children.Clear()
             PanProjects.Children.Clear()
-            If Loader.Input.SearchFilter = "" Then
-                CardProjects.Title = "热门整合包"
-            Else
-                CardProjects.Title = "搜索结果 (" & Loader.Output.Projects.Count & If(Loader.Output.Projects.Count < Loader.Output.RealCount, "+", "") & ")"
-            End If
-            For Each Project In Loader.Output.Projects
-                PanProjects.Children.Add(Project.ToCfItem(Loader.Input.GameVersion Is Nothing, Loader.Input.ModLoader Is Nothing OrElse Loader.Input.ModLoader = 0, AddressOf ProjectClick))
+            For i = Math.Min(Page * PageSize, Storage.Results.Count - 1) To Math.Min((Page + 1) * PageSize - 1, Storage.Results.Count - 1)
+                PanProjects.Children.Add(
+                    Storage.Results(i).ToCompItem(Loader.Input.GameVersion Is Nothing, True, AddressOf ProjectClick))
             Next
             Next
+            '页码
+            CardPages.Visibility = If(Storage.Results.Count > 40 OrElse
+                                      Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal,
+                                      Visibility.Visible, Visibility.Collapsed)
+            LabPage.Text = Page + 1
+            BtnPageLeft.IsEnabled = Page > 0
+            BtnPageLeft.Opacity = If(BtnPageLeft.IsEnabled, 1, 0.5)
+            BtnPageRight.IsEnabled = Storage.Results.Count > PageSize * (Page + 1) OrElse
+                                     Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal
+            BtnPageRight.Opacity = If(BtnPageRight.IsEnabled, 1, 0.5)
+            '强制返回顶部
+            PanBack.ScrollToTop()
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "可视化整合包列表出错", LogLevel.Feedback)
             Log(ex, "可视化整合包列表出错", LogLevel.Feedback)
         End Try
         End Try
     End Sub
     End Sub
+
     '自动重试
     '自动重试
     Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged
     Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged
         Select Case Loader.State
         Select Case Loader.State
             Case LoadState.Failed
             Case LoadState.Failed
                 Dim ErrorMessage As String = ""
                 Dim ErrorMessage As String = ""
-                If DlCfFileLoader.Error IsNot Nothing Then ErrorMessage = DlCfFileLoader.Error.Message
+                If Loader.Error IsNot Nothing Then ErrorMessage = Loader.Error.Message
                 If ErrorMessage.Contains("不是有效的 Json 文件") Then
                 If ErrorMessage.Contains("不是有效的 Json 文件") Then
                     Log("[Download] 下载的整合包列表 Json 文件损坏,已自动重试", LogLevel.Debug)
                     Log("[Download] 下载的整合包列表 Json 文件损坏,已自动重试", LogLevel.Debug)
                     PageLoaderRestart()
                     PageLoaderRestart()
@@ -47,28 +61,49 @@
         End Select
         End Select
     End Sub
     End Sub
 
 
-    Private Sub Load_OnStart()
-        PanLoad.Visibility = Visibility.Visible
-        AniStart({
-                 AaOpacity(PanLoad, 1 - PanLoad.Opacity, 150),
-                 AaOpacity(CardProjects, -CardProjects.Opacity, 150),
-                 AaCode(Sub()
-                            CardProjects.Visibility = Visibility.Collapsed
-                            PanProjects.Children.Clear()
-                        End Sub,, True)
-            }, "FrmDownloadPack Load Switch")
+    '进入详情页面
+
+    Public Sub ProjectClick(sender As MyCompItem, e As EventArgs)
+        FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail, .Additional = sender.Tag})
     End Sub
     End Sub
 
 
-    Public Sub ProjectClick(sender As MyCfItem, e As EventArgs)
-        FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CfDetail, .Additional = sender.Tag})
+    '切换页码
+
+    Private Sub BtnPageLeft_Click(sender As Object, e As RoutedEventArgs) Handles BtnPageLeft.Click
+        ChangePage(Page - 1)
+    End Sub
+    Private Sub BtnPageRight_Click(sender As Object, e As RoutedEventArgs) Handles BtnPageRight.Click
+        ChangePage(Page + 1)
+    End Sub
+    Private Sub ChangePage(NewPage As Integer)
+        CardPages.IsEnabled = False
+        Page = NewPage
+        FrmMain.BackToTop()
+        Log($"[Download] 整合包切换到第 {Page + 1} 页")
+        RunInThread(Sub()
+                        Thread.Sleep(100) '等待向上滚的动画结束
+                        RunInUi(Sub() CardPages.IsEnabled = True)
+                        Loader.Start()
+                    End Sub)
     End Sub
     End Sub
 
 
-    Private Sub TextSearchName_KeyUp(sender As Object, e As KeyEventArgs) Handles TextSearchName.KeyUp, TextSearchVersion.KeyUp
-        If e.Key = Key.Enter Then StartSearch()
+    Private Sub BtnSearchInstall_Click(sender As Object, e As EventArgs) Handles BtnSearchInstall.Click
+        ModpackInstall()
     End Sub
     End Sub
-    Private Shared Sub StartSearch() Handles BtnSearchRun.Click
+
+#Region "搜索"
+
+    '搜索按钮
+    Private Sub StartNewSearch() Handles BtnSearchRun.Click
+        Page = 0
+        Storage = New CompProjectStorage
         Loader.Start()
         Loader.Start()
     End Sub
     End Sub
+    Private Sub TextSearchName_KeyUp(sender As Object, e As KeyEventArgs) Handles TextSearchName.KeyUp, TextSearchVersion.KeyUp
+        If e.Key = Key.Enter Then StartNewSearch()
+    End Sub
+
+    '重置按钮
     Private Sub BtnSearchReset_Click(sender As Object, e As EventArgs) Handles BtnSearchReset.Click
     Private Sub BtnSearchReset_Click(sender As Object, e As EventArgs) Handles BtnSearchReset.Click
         TextSearchName.Text = ""
         TextSearchName.Text = ""
         TextSearchVersion.SelectedIndex = 0
         TextSearchVersion.SelectedIndex = 0
@@ -76,8 +111,7 @@
         ComboSearchTag.SelectedIndex = 0
         ComboSearchTag.SelectedIndex = 0
         Loader.LastFinishedTime = 0 '要求强制重新开始
         Loader.LastFinishedTime = 0 '要求强制重新开始
     End Sub
     End Sub
-    Private Sub BtnSearchInstall_Click(sender As Object, e As EventArgs) Handles BtnSearchInstall.Click
-        ModpackInstall()
-    End Sub
+
+#End Region
 
 
 End Class
 End Class

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

@@ -347,7 +347,7 @@ StartDownload:
         If Content.Contains("'portssub' from ") Then LastPortsId = Content.Split(" ").Last
         If Content.Contains("'portssub' from ") Then LastPortsId = Content.Split(" ").Last
         If Content.Contains("Listening tcp ") Then
         If Content.Contains("Listening tcp ") Then
             If UserList.ContainsKey(LastPortsId) Then
             If UserList.ContainsKey(LastPortsId) Then
-                DictionaryAdd(UserList(LastPortsId).Ports, Content.Split(" ").Last, RegexSeek(Content, "(?<=Listening tcp )[^:]+"))
+                UserList(LastPortsId).Ports(Content.Split(" ").Last) = RegexSeek(Content, "(?<=Listening tcp )[^:]+")
             Else
             Else
                 Log("[IOI] 未在列表中的用户出现意料外的连接信息")
                 Log("[IOI] 未在列表中的用户出现意料外的连接信息")
                 NetRequestOnce("http://127.0.0.1:55555/api/link?id=" & LastPortsId & "&password=" & IoiPassword, "DELETE", "", "", 500)
                 NetRequestOnce("http://127.0.0.1:55555/api/link?id=" & LastPortsId & "&password=" & IoiPassword, "DELETE", "", "", 500)
@@ -574,7 +574,7 @@ Done:
                 Rooms.Add(RoomObject)
                 Rooms.Add(RoomObject)
             Next
             Next
             RawJson("rooms") = Rooms
             RawJson("rooms") = Rooms
-            DictionaryAdd(User.PingPending, Unique, Date.Now)
+            User.PingPending(Unique) = Date.Now
         End If
         End If
         User.Send(RawJson)
         User.Send(RawJson)
     End Sub
     End Sub
@@ -1179,7 +1179,7 @@ Done:
                     If Id > IoiId Then
                     If Id > IoiId Then
                         Log("[IOI] 双向连接,应当抛弃当前用户(" & DisplayName & ")")
                         Log("[IOI] 双向连接,应当抛弃当前用户(" & DisplayName & ")")
                         For Each Pair In UserList(User.Id).Ports.ToList
                         For Each Pair In UserList(User.Id).Ports.ToList
-                            DictionaryAdd(User.Ports, Pair.Key, Pair.Value)
+                            User.Ports(Pair.Key) = Pair.Value
                         Next
                         Next
                         UserList(Id).Dispose()
                         UserList(Id).Dispose()
                     Else
                     Else

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

@@ -417,7 +417,7 @@ PreFin:
         If ComboArgumentJava Is Nothing Then Exit Sub
         If ComboArgumentJava Is Nothing Then Exit Sub
         '初始化列表
         '初始化列表
         ComboArgumentJava.Items.Clear()
         ComboArgumentJava.Items.Clear()
-        ComboArgumentJava.Items.Add(New MyComboBoxItem With {.Content = "自动选择适合所选游戏版本的 Java", .Tag = "自动选择"})
+        ComboArgumentJava.Items.Add(New MyComboBoxItem With {.Content = "自动选择合适的 Java", .Tag = "自动选择"})
         '更新列表
         '更新列表
         Dim SelectedItem As MyComboBoxItem = Nothing
         Dim SelectedItem As MyComboBoxItem = Nothing
         Dim SelectedBySetup As String = Setup.Get("LaunchArgumentJavaSelect")
         Dim SelectedBySetup As String = Setup.Get("LaunchArgumentJavaSelect")

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

@@ -47,9 +47,9 @@
                         <local:MySlider x:Name="SliderDownloadSpeed" Grid.Row="5" Tag="ToolDownloadSpeed" MaxValue="42" Value="42" Grid.Column="1" />
                         <local:MySlider x:Name="SliderDownloadSpeed" Grid.Row="5" Tag="ToolDownloadSpeed" MaxValue="42" Value="42" Grid.Column="1" />
                         <TextBlock VerticalAlignment="Top" Grid.Row="6" HorizontalAlignment="Left" Text="目标文件夹" Margin="0,5,25,5" />
                         <TextBlock VerticalAlignment="Top" Grid.Row="6" HorizontalAlignment="Left" Text="目标文件夹" Margin="0,5,25,5" />
                         <TextBlock Margin="0,5" HorizontalAlignment="Left" Text="请在 启动 → 版本选择 → 文件夹列表 中更改下载目标文件夹。&#xa;在某个文件夹或游戏版本上右键,即可选择打开对应文件夹。" Grid.Row="6" Grid.Column="1" VerticalAlignment="Center" />
                         <TextBlock Margin="0,5" HorizontalAlignment="Left" Text="请在 启动 → 版本选择 → 文件夹列表 中更改下载目标文件夹。&#xa;在某个文件夹或游戏版本上右键,即可选择打开对应文件夹。" Grid.Row="6" Grid.Column="1" VerticalAlignment="Center" />
-                        <local:MyCheckBox Margin="0,2,0,4" Text="安装 CurseForge 整合包时保留原始压缩包" Grid.Row="7" Height="22" Grid.ColumnSpan="2"
+                        <local:MyCheckBox Margin="0,2,0,4" Text="安装整合包时保留原始压缩包" Grid.Row="7" Height="22" Grid.ColumnSpan="2"
                                           x:Name="CheckDownloadKeepModpack" Tag="ToolDownloadKeepModpack" 
                                           x:Name="CheckDownloadKeepModpack" Tag="ToolDownloadKeepModpack" 
-                                          ToolTip="安装 CurseForge 整合包后,在版本文件夹下保留用于安装的 原始整合包.zip 文件" />
+                                          ToolTip="安装整合包时,在版本文件夹下保留用于安装的原始整合包压缩包文件" />
                     </Grid>
                     </Grid>
                 </StackPanel>
                 </StackPanel>
             </local:MyCard>
             </local:MyCard>

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

@@ -412,7 +412,7 @@ PreFin:
         '初始化列表
         '初始化列表
         ComboArgumentJava.Items.Clear()
         ComboArgumentJava.Items.Clear()
         ComboArgumentJava.Items.Add(New MyComboBoxItem With {.Content = "使用全局设置", .Tag = "使用全局设置"})
         ComboArgumentJava.Items.Add(New MyComboBoxItem With {.Content = "使用全局设置", .Tag = "使用全局设置"})
-        ComboArgumentJava.Items.Add(New MyComboBoxItem With {.Content = "自动选择适合所选游戏版本的 Java", .Tag = "自动选择"})
+        ComboArgumentJava.Items.Add(New MyComboBoxItem With {.Content = "自动选择合适的 Java", .Tag = "自动选择"})
         '更新列表
         '更新列表
         Dim SelectedItem As MyComboBoxItem = Nothing
         Dim SelectedItem As MyComboBoxItem = Nothing
         Dim SelectedBySetup As String = Setup.Get("VersionArgumentJavaSelect", Version:=PageVersionLeft.Version)
         Dim SelectedBySetup As String = Setup.Get("VersionArgumentJavaSelect", Version:=PageVersionLeft.Version)

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

@@ -182,6 +182,7 @@
     <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="Modules\Minecraft\ModComp.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">
@@ -217,8 +218,8 @@
       <SubType>Component</SubType>
       <SubType>Component</SubType>
     </Compile>
     </Compile>
     <Compile Include="Pages\PageDownload\ModDownloadLib.vb" />
     <Compile Include="Pages\PageDownload\ModDownloadLib.vb" />
-    <Compile Include="Pages\PageDownload\MyCfItem.xaml.vb">
-      <DependentUpon>MyCfItem.xaml</DependentUpon>
+    <Compile Include="Modules\Minecraft\MyCompItem.xaml.vb">
+      <DependentUpon>MyCompItem.xaml</DependentUpon>
     </Compile>
     </Compile>
     <Compile Include="Pages\PageDownload\PageDownloadMod.xaml.vb">
     <Compile Include="Pages\PageDownload\PageDownloadMod.xaml.vb">
       <DependentUpon>PageDownloadMod.xaml</DependentUpon>
       <DependentUpon>PageDownloadMod.xaml</DependentUpon>
@@ -229,8 +230,8 @@
     <Compile Include="Pages\PageDownload\PageDownloadInstall.xaml.vb">
     <Compile Include="Pages\PageDownload\PageDownloadInstall.xaml.vb">
       <DependentUpon>PageDownloadInstall.xaml</DependentUpon>
       <DependentUpon>PageDownloadInstall.xaml</DependentUpon>
     </Compile>
     </Compile>
-    <Compile Include="Pages\PageDownload\PageDownloadCfDetail.xaml.vb">
-      <DependentUpon>PageDownloadCfDetail.xaml</DependentUpon>
+    <Compile Include="Pages\PageDownload\PageDownloadCompDetail.xaml.vb">
+      <DependentUpon>PageDownloadCompDetail.xaml</DependentUpon>
     </Compile>
     </Compile>
     <Compile Include="Pages\PageDownload\PageDownloadPack.xaml.vb">
     <Compile Include="Pages\PageDownload\PageDownloadPack.xaml.vb">
       <DependentUpon>PageDownloadPack.xaml</DependentUpon>
       <DependentUpon>PageDownloadPack.xaml</DependentUpon>
@@ -459,7 +460,7 @@
       <DependentUpon>FormMain.xaml</DependentUpon>
       <DependentUpon>FormMain.xaml</DependentUpon>
       <SubType>Code</SubType>
       <SubType>Code</SubType>
     </Compile>
     </Compile>
-    <Page Include="Pages\PageDownload\MyCfItem.xaml">
+    <Page Include="Modules\Minecraft\MyCompItem.xaml">
       <Generator>MSBuild:Compile</Generator>
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
       <SubType>Designer</SubType>
     </Page>
     </Page>
@@ -475,7 +476,7 @@
       <Generator>MSBuild:Compile</Generator>
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
       <SubType>Designer</SubType>
     </Page>
     </Page>
-    <Page Include="Pages\PageDownload\PageDownloadCfDetail.xaml">
+    <Page Include="Pages\PageDownload\PageDownloadCompDetail.xaml">
       <SubType>Designer</SubType>
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
       <Generator>MSBuild:Compile</Generator>
     </Page>
     </Page>

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 136 - 136
Plain Craft Launcher 2/Resources/ModData.txt


BIN
Plain Craft Launcher 2/Resources/java-wrapper.jar


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно