Procházet zdrojové kódy

Merge pull request #4291 from youzi-2333/feat-setup-modsrc

添加国内 Mod 镜像下载源
龙腾猫跃 před 2 roky
rodič
revize
1c6af2caca

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

@@ -1,7 +1,8 @@
 Imports System.Net
 Imports System.Net
-Imports PCL.ModNet
 
 
 Public Module ModNet
 Public Module ModNet
+
+
     Public Const NetDownloadEnd As String = ".PCLDownloading"
     Public Const NetDownloadEnd As String = ".PCLDownloading"
 
 
     ''' <summary>
     ''' <summary>

+ 36 - 11
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb

@@ -947,7 +947,7 @@ Retry:
                         Try
                         Try
                             '获取工程列表
                             '获取工程列表
                             Log("[Comp] 开始从 CurseForge 获取工程列表:" & CurseForgeUrl)
                             Log("[Comp] 开始从 CurseForge 获取工程列表:" & CurseForgeUrl)
-                            Dim RequestResult As JObject = NetGetCodeByRequestRetry(CurseForgeUrl, IsJson:=True, Encode:=Encoding.UTF8)
+                            Dim RequestResult As JObject = DlModRequest(CurseForgeUrl, IsJson:=True)
                             Task.Progress += 0.2
                             Task.Progress += 0.2
                             Dim ProjectList As New List(Of CompProject)
                             Dim ProjectList As New List(Of CompProject)
                             For Each JsonEntry As JObject In RequestResult("data")
                             For Each JsonEntry As JObject In RequestResult("data")
@@ -977,7 +977,7 @@ Retry:
                     Sub()
                     Sub()
                         Try
                         Try
                             Log("[Comp] 开始从 Modrinth 获取工程列表:" & ModrinthUrl)
                             Log("[Comp] 开始从 Modrinth 获取工程列表:" & ModrinthUrl)
-                            Dim RequestResult As JObject = NetGetCodeByRequestRetry(ModrinthUrl, IsJson:=True, Encode:=Encoding.UTF8)
+                            Dim RequestResult As JObject = DlModRequest(ModrinthUrl, IsJson:=True)
                             Task.Progress += 0.2
                             Task.Progress += 0.2
                             Dim ProjectList As New List(Of CompProject)
                             Dim ProjectList As New List(Of CompProject)
                             For Each JsonEntry As JObject In RequestResult("hits")
                             For Each JsonEntry As JObject In RequestResult("hits")
@@ -1190,6 +1190,10 @@ Retry:
         ''' </summary>
         ''' </summary>
         Public DownloadUrls As List(Of String)
         Public DownloadUrls As List(Of String)
         ''' <summary>
         ''' <summary>
+        ''' 文件所有可能的镜像源。
+        ''' </summary>
+        Public MirrorUrls As List(Of String)
+        ''' <summary>
         ''' 文件的 SHA1 或 MD5。
         ''' 文件的 SHA1 或 MD5。
         ''' </summary>
         ''' </summary>
         Public ReadOnly Hash As String = Nothing
         Public ReadOnly Hash As String = Nothing
@@ -1207,7 +1211,9 @@ Retry:
         ''' </summary>
         ''' </summary>
         ''' <param name="LocalAddress">目标本地文件夹,或完整的文件路径。会自动判断类型。</param>
         ''' <param name="LocalAddress">目标本地文件夹,或完整的文件路径。会自动判断类型。</param>
         Public Function ToNetFile(LocalAddress As String) As NetFile
         Public Function ToNetFile(LocalAddress As String) As NetFile
-            Return New NetFile(DownloadUrls, LocalAddress & If(LocalAddress.EndsWithF("\"), FileName, ""), New FileChecker(Hash:=Hash), UseBrowserUserAgent:=True)
+            Dim urls As List(Of String) = DownloadUrls
+            urls.AddRange(MirrorUrls)
+            Return New NetFile(urls, LocalAddress & If(LocalAddress.EndsWithF("\"), FileName, ""), New FileChecker(Hash:=Hash), UseBrowserUserAgent:=True)
         End Function
         End Function
 
 
         '实例化
         '实例化
@@ -1325,6 +1331,11 @@ Retry:
                     If RawLoaders.Contains("quilt") Then ModLoaders.Add(CompModLoaderType.Quilt)
                     If RawLoaders.Contains("quilt") Then ModLoaders.Add(CompModLoaderType.Quilt)
 #End Region
 #End Region
                 End If
                 End If
+                '末尾添加备用镜像 URL
+                MirrorUrls = New List(Of String)
+                For Each u In DownloadUrls
+                    MirrorUrls.Add(DlSourceModGet(u))
+                Next
             End If
             End If
         End Sub
         End Sub
         ''' <summary>
         ''' <summary>
@@ -1426,10 +1437,24 @@ Retry:
         Dim TargetProject As CompProject
         Dim TargetProject As CompProject
         If CompProjectCache.ContainsKey(ProjectId) Then '存在缓存
         If CompProjectCache.ContainsKey(ProjectId) Then '存在缓存
             TargetProject = CompProjectCache(ProjectId)
             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))
+        Else
+            Dim retryCount As Integer = 0
+Retry:
+            Dim comp As Object
+            If FromCurseForge Then 'CurseForge
+                comp = DlModRequest("https://api.curseforge.com/v1/mods/" & ProjectId, IsJson:=True)
+                TargetProject = New CompProject(DlModRequest("https://api.curseforge.com/v1/mods/" & ProjectId, IsJson:=True)("data"))
+            Else 'Modrinth
+                comp = DlModRequest("https://api.modrinth.com/v2/project/" & ProjectId, IsJson:=True)
+                TargetProject = New CompProject(DlModRequest("https://api.modrinth.com/v2/project/" & ProjectId, IsJson:=True))
+            End If
+            If comp Is Nothing Then '获取 ("data") 时会报值不能为 null
+                If retryCount > 2 Then Throw New Exception("工程对象获取失败,请等待一段时间后刷新再试!")
+                retryCount += 1
+                Thread.Sleep(1000)
+                GoTo Retry
+            End If
+            TargetProject = New CompProject(If(FromCurseForge, comp("data"), comp))
         End If
         End If
         '获取工程对象的文件列表
         '获取工程对象的文件列表
         Log("[Comp] 开始获取文件列表:" & ProjectId)
         Log("[Comp] 开始获取文件列表:" & ProjectId)
@@ -1437,13 +1462,13 @@ Retry:
         If FromCurseForge Then
         If FromCurseForge Then
             'CurseForge
             'CurseForge
             If TargetProject.Type = CompType.Mod Then 'Mod 使用每个版本最新的文件
             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")
+                ResultJsonArray = GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(TargetProject.CurseForgeFileIds, ",") & "]}", "application/json"))("data")
             Else '否则使用全部文件
             Else '否则使用全部文件
-                ResultJsonArray = NetGetCodeByRequestRetry($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999", Accept:="application/json", IsJson:=True)("data")
+                ResultJsonArray = NetGetCodeByRequestRetry($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999", Accept:="application/json", IsJson:=True, BackupUrl:=DlSourceModGet($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999"))("data")
             End If
             End If
         Else
         Else
             'Modrinth
             'Modrinth
-            ResultJsonArray = NetGetCodeByRequestRetry($"https://api.modrinth.com/v2/project/{ProjectId}/version", Accept:="application/json", IsJson:=True)
+            ResultJsonArray = NetGetCodeByRequestRetry($"https://api.modrinth.com/v2/project/{ProjectId}/version", Accept:="application/json", IsJson:=True, BackupUrl:=DlSourceModGet($"https://api.modrinth.com/v2/project/{ProjectId}/version"))
         End If
         End If
         CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Type)).
         CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Type)).
             Where(Function(a) a.Available).ToList.Distinct(Function(a, b) a.Id = b.Id) 'CurseForge 可能会重复返回相同项(#1330)
             Where(Function(a) a.Available).ToList.Distinct(Function(a, b) a.Id = b.Id) 'CurseForge 可能会重复返回相同项(#1330)
@@ -1457,7 +1482,7 @@ Retry:
             Log($"[Comp] {ProjectId} 文件列表中还需要获取信息的前置 Mod:{Join(UndoneDeps, ",")}")
             Log($"[Comp] {ProjectId} 文件列表中还需要获取信息的前置 Mod:{Join(UndoneDeps, ",")}")
             Dim Projects As JArray
             Dim Projects As JArray
             If TargetProject.FromCurseForge Then
             If TargetProject.FromCurseForge Then
-                Projects = GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods",
+                Projects = GetJson(DlModRequest("https://api.curseforge.com/v1/mods",
                     "POST", "{""modIds"": [" & Join(UndoneDeps, ",") & "]}", "application/json"))("data")
                     "POST", "{""modIds"": [" & Join(UndoneDeps, ",") & "]}", "application/json"))("data")
             Else
             Else
                 Projects = NetGetCodeByRequestRetry($"https://api.modrinth.com/v2/projects?ids=[""{Join(UndoneDeps, """,""")}""]",
                 Projects = NetGetCodeByRequestRetry($"https://api.modrinth.com/v2/projects?ids=[""{Join(UndoneDeps, """,""")}""]",

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

@@ -1,4 +1,6 @@
-Public Module ModDownload
+Imports Newtonsoft
+
+Public Module ModDownload
 
 
 #Region "DlClient* | Minecraft 客户端"
 #Region "DlClient* | Minecraft 客户端"
 
 
@@ -1083,6 +1085,69 @@
 
 
 #End Region
 #End Region
 
 
+#Region "DlMod* | Mod 列表 / 信息 / 下载"
+    Public Function DlModRequest(Url As String, Optional IsJson As Boolean = False) As String
+        Dim McimUrl As String = DlSourceModGet(Url)
+        Dim Urls As New List(Of KeyValuePair(Of String, Integer))
+        If McimUrl <> Url Then
+            Select Case Setup.Get("ToolDownloadVersion")
+                Case 0
+                    Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30))
+                    Urls.Add(New KeyValuePair(Of String, Integer)(Url, 60))
+                Case 1
+                    Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5))
+                    Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 35))
+                Case Else
+                    Urls.Add(New KeyValuePair(Of String, Integer)(Url, 60))
+                    Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 60))
+            End Select
+        End If
+        Return DlModRequest(Urls, IsJson:=IsJson)
+    End Function
+    Public Function DlModRequest(Url As String, Method As String, Data As String, ContentType As String,
+                                 Optional Headers As Dictionary(Of String, String) = Nothing) As String
+        Dim McimUrl As String = DlSourceModGet(Url)
+        Dim Urls As New List(Of KeyValuePair(Of String, Integer))
+        If McimUrl <> Url Then
+            Select Case Setup.Get("ToolDownloadVersion")
+                Case 0
+                    Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30))
+                    Urls.Add(New KeyValuePair(Of String, Integer)(Url, 60))
+                Case 1
+                    Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5))
+                    Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 35))
+                Case Else
+                    Urls.Add(New KeyValuePair(Of String, Integer)(Url, 60))
+                    Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 60))
+            End Select
+        End If
+        Return DlModRequest(Urls, Method, Data, ContentType, Headers:=Headers)
+    End Function
+    Private Function DlModRequest(Urls As List(Of KeyValuePair(Of String, Integer)), Optional IsJson As Boolean = False) As String
+        Dim exMessage As String = ""
+        For Each url In Urls
+            Try
+                Return NetGetCodeByDownload(url.Key, Timeout:=url.Value, IsJson:=IsJson)
+            Catch ex As Exception
+                exMessage += ex.Message + vbCrLf
+            End Try
+        Next
+        Throw New Exception(exMessage)
+    End Function
+    Private Function DlModRequest(Urls As List(Of KeyValuePair(Of String, Integer)), Method As String, Data As String,
+                                  ContentType As String, Optional Headers As Dictionary(Of String, String) = Nothing) As String
+        Dim exMessage As String = ""
+        For Each url In Urls
+            Try
+                Return NetRequestOnce(url.Key, Method, Data, ContentType, Timeout:=url.Value, Headers:=Headers)
+            Catch ex As Exception
+                exMessage += ex.Message + vbCrLf
+            End Try
+        Next
+        Throw New Exception(exMessage)
+    End Function
+#End Region
+
 #Region "DlSource | 镜像下载源"
 #Region "DlSource | 镜像下载源"
 
 
     Public Function DlSourceResourceGet(MojangBase As String) As String()
     Public Function DlSourceResourceGet(MojangBase As String) As String()
@@ -1100,6 +1165,16 @@
             MojangBase
             MojangBase
         }
         }
     End Function
     End Function
+    Public Function DlSourceModGet(CfMrBase As String) As String
+        'If CfMrBase Is Nothing Then Throw New Exception("无对应的 Mod 下载地址")
+        Return CfMrBase.Replace("api.modrinth.com/v2", "mod.mcmirror.top/modrinth") _
+            .Replace("staging-api.modrinth.com/v2", "mod.mcmirror.top/modrinth") _
+            .Replace("cdn.modrinth.com", "mod.mcimirror.top") _
+            .Replace("api.curseforge.com", "mod.mcimirror.top/curseforge") _
+            .Replace("edge.forgecdn.net", "mod.mcimirror.top") _
+            .Replace("mediafilez.forgecdn.net", "mod.mcimirror.top") _
+            .Replace("media.forgecdn.net", "mod.mcimirror.top")
+    End Function
 
 
     Public Function DlSourceLauncherOrMetaGet(MojangBase As String) As String()
     Public Function DlSourceLauncherOrMetaGet(MojangBase As String) As String()
         If MojangBase Is Nothing Then Throw New Exception("无对应的 json 下载地址")
         If MojangBase Is Nothing Then Throw New Exception("无对应的 json 下载地址")

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

@@ -952,7 +952,7 @@ Finished:
             Try
             Try
                 '步骤 1:获取 Hash 与对应的工程 ID
                 '步骤 1:获取 Hash 与对应的工程 ID
                 Dim ModrinthHashes = Mods.Select(Function(m) m.ModrinthHash).ToList()
                 Dim ModrinthHashes = Mods.Select(Function(m) m.ModrinthHash).ToList()
-                Dim ModrinthVersion = CType(GetJson(NetRequestRetry("https://api.modrinth.com/v2/version_files", "POST",
+                Dim ModrinthVersion = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files", "POST",
                     $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")), JObject)
                     $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")), JObject)
                 Log($"[Mod] 从 Modrinth 获取到 {ModrinthVersion.Count} 个本地 Mod 的对应信息")
                 Log($"[Mod] 从 Modrinth 获取到 {ModrinthVersion.Count} 个本地 Mod 的对应信息")
                 '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
                 '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
@@ -985,7 +985,7 @@ Finished:
                 Next
                 Next
                 Log($"[Mod] 已从 Modrinth 获取本地 Mod 信息,继续获取更新信息")
                 Log($"[Mod] 已从 Modrinth 获取本地 Mod 信息,继续获取更新信息")
                 '步骤 4:获取更新信息
                 '步骤 4:获取更新信息
-                Dim ModrinthUpdate = CType(GetJson(NetRequestRetry("https://api.modrinth.com/v2/version_files/update", "POST",
+                Dim ModrinthUpdate = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files/update", "POST",
                     $"{{""hashes"": [""{ModrinthMapping.SelectMany(Function(l) l.Value.Select(Function(m) m.ModrinthHash)).Join(""",""")}""], ""algorithm"": ""sha1"", 
                     $"{{""hashes"": [""{ModrinthMapping.SelectMany(Function(l) l.Value.Select(Function(m) m.ModrinthHash)).Join(""",""")}""], ""algorithm"": ""sha1"", 
                     ""loaders"": [""{ModLoaders.Join(""",""").ToLower}""],""game_versions"": [""{McVersion}""]}}", "application/json")), JObject)
                     ""loaders"": [""{ModLoaders.Join(""",""").ToLower}""],""game_versions"": [""{McVersion}""]}}", "application/json")), JObject)
                 For Each Entry In Mods
                 For Each Entry In Mods
@@ -1022,7 +1022,7 @@ Finished:
                     CurseForgeHashes.Add(Entry.CurseForgeHash)
                     CurseForgeHashes.Add(Entry.CurseForgeHash)
                     If Loader.IsAbortedWithThread(MainThread) Then Exit Sub
                     If Loader.IsAbortedWithThread(MainThread) Then Exit Sub
                 Next
                 Next
-                Dim CurseForgeRaw = CType(CType(GetJson(NetRequestRetry("https://api.curseforge.com/v1/fingerprints/432/", "POST",
+                Dim CurseForgeRaw = CType(CType(GetJson(DlModRequest("https://api.curseforge.com/v1/fingerprints/432/", "POST",
                                     $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer)
                                     $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer)
                 Log($"[Mod] 从 CurseForge 获取到 {CurseForgeRaw.Count} 个本地 Mod 的对应信息")
                 Log($"[Mod] 从 CurseForge 获取到 {CurseForgeRaw.Count} 个本地 Mod 的对应信息")
                 '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
                 '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
@@ -1045,7 +1045,7 @@ Finished:
                 Log($"[Mod] 需要从 CurseForge 获取 {CurseForgeMapping.Count} 个本地 Mod 的工程信息")
                 Log($"[Mod] 需要从 CurseForge 获取 {CurseForgeMapping.Count} 个本地 Mod 的工程信息")
                 '步骤 3:获取工程信息
                 '步骤 3:获取工程信息
                 If Not CurseForgeMapping.Any() Then Exit Sub
                 If Not CurseForgeMapping.Any() Then Exit Sub
-                Dim CurseForgeProject = CType(GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods", "POST",
+                Dim CurseForgeProject = CType(GetJson(DlModRequest("https://api.curseforge.com/v1/mods", "POST",
                                     $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")), JObject)("data")
                                     $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")), JObject)("data")
                 Dim UpdateFileIds As New Dictionary(Of Integer, List(Of McMod)) 'FileId -> 本地 Mod 文件列表
                 Dim UpdateFileIds As New Dictionary(Of Integer, List(Of McMod)) 'FileId -> 本地 Mod 文件列表
                 Dim FileIdToProjectSlug As New Dictionary(Of Integer, String)
                 Dim FileIdToProjectSlug As New Dictionary(Of Integer, String)
@@ -1087,7 +1087,7 @@ Finished:
                 Log($"[Mod] 已从 CurseForge 获取本地 Mod 信息,需要获取 {UpdateFileIds.Count} 个用于检查更新的文件信息")
                 Log($"[Mod] 已从 CurseForge 获取本地 Mod 信息,需要获取 {UpdateFileIds.Count} 个用于检查更新的文件信息")
                 '步骤 4:获取更新文件信息
                 '步骤 4:获取更新文件信息
                 If Not UpdateFileIds.Any() Then Exit Sub
                 If Not UpdateFileIds.Any() Then Exit Sub
-                Dim CurseForgeFiles = CType(GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods/files", "POST",
+                Dim CurseForgeFiles = CType(GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST",
                                     $"{{""fileIds"": [{UpdateFileIds.Keys.Join(",")}]}}", "application/json")), JObject)("data")
                                     $"{{""fileIds"": [{UpdateFileIds.Keys.Join(",")}]}}", "application/json")), JObject)("data")
                 Dim UpdateFiles As New Dictionary(Of McMod, CompFile)
                 Dim UpdateFiles As New Dictionary(Of McMod, CompFile)
                 For Each FileJson In CurseForgeFiles
                 For Each FileJson In CurseForgeFiles

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

@@ -108,6 +108,8 @@
         {"ToolDownloadKeepModpack", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolDownloadKeepModpack", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolDownloadIgnoreQuilt", New SetupEntry(True, Source:=SetupSource.Registry)},
         {"ToolDownloadIgnoreQuilt", New SetupEntry(True, Source:=SetupSource.Registry)},
         {"ToolDownloadCert", New SetupEntry(True, Source:=SetupSource.Registry)},
         {"ToolDownloadCert", New SetupEntry(True, Source:=SetupSource.Registry)},
+        {"ToolDownloadMod", New SetupEntry(0, Source:=SetupSource.Registry)},
+        {"ToolDownloadModMirror", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolUpdateAlpha", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
         {"ToolUpdateAlpha", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
         {"ToolUpdateRelease", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolUpdateRelease", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolUpdateSnapshot", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"ToolUpdateSnapshot", New SetupEntry(False, Source:=SetupSource.Registry)},

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

@@ -20,6 +20,9 @@
                             <RowDefinition Height="7" />
                             <RowDefinition Height="7" />
                             <RowDefinition Height="28" />
                             <RowDefinition Height="28" />
                             <RowDefinition Height="7" />
                             <RowDefinition Height="7" />
+                            <RowDefinition Height="28" />
+                            <RowDefinition Height="7" />
+                            <RowDefinition Height="Auto" />
                             <RowDefinition Height="27" />
                             <RowDefinition Height="27" />
                             <RowDefinition Height="27" />
                             <RowDefinition Height="27" />
                             <RowDefinition Height="Auto" />
                             <RowDefinition Height="Auto" />
@@ -42,21 +45,27 @@
                             <local:MyComboBoxItem Content="译名位于文件名末尾:relics-0.1.14-遗物" />
                             <local:MyComboBoxItem Content="译名位于文件名末尾:relics-0.1.14-遗物" />
                             <local:MyComboBoxItem Content="不添加中文译名:relics-0.1.14" />
                             <local:MyComboBoxItem Content="不添加中文译名:relics-0.1.14" />
                         </local:MyComboBox>
                         </local:MyComboBox>
-                        <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="最大线程数" Margin="0,0,25,0" />
-                        <local:MySlider x:Name="SliderDownloadThread" Grid.Row="4" Tag="ToolDownloadThread" MaxValue="255" Value="63" Grid.Column="1"
+                        <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="Mod 信息获取" Margin="0,0,25,0" />
+                        <local:MyComboBox x:Name="ComboDownloadMod" Grid.Row="4" Grid.ColumnSpan="2" Tag="ToolDownloadMod" Grid.Column="1">
+                            <local:MyComboBoxItem Content="尽量使用官方源" IsSelected="True" />
+                            <local:MyComboBoxItem Content="在官方源加载缓慢时改用镜像源" />
+                            <local:MyComboBoxItem Content="尽量使用镜像源" />
+                        </local:MyComboBox>
+                        <TextBlock Grid.Row="7" VerticalAlignment="Center" HorizontalAlignment="Left" Text="最大线程数" Margin="0,0,25,0" />
+                        <local:MySlider x:Name="SliderDownloadThread" Grid.Row="7" Tag="ToolDownloadThread" MaxValue="255" Value="63" Grid.Column="1"
                                         ToolTip="线程数越多,限速的文件下载越快,但过高的线程数会造成下载时非常严重的卡顿。&#xa;一般而言,64 线程已可以保证足够的下载速度。" />
                                         ToolTip="线程数越多,限速的文件下载越快,但过高的线程数会造成下载时非常严重的卡顿。&#xa;一般而言,64 线程已可以保证足够的下载速度。" />
-                        <TextBlock VerticalAlignment="Center" Grid.Row="5" HorizontalAlignment="Left" Text="速度限制" Margin="0,0,25,0" />
-                        <local:MySlider x:Name="SliderDownloadSpeed" Grid.Row="5" Tag="ToolDownloadSpeed" MaxValue="42" Value="42" Grid.Column="1"
+                        <TextBlock VerticalAlignment="Center" Grid.Row="8" HorizontalAlignment="Left" Text="速度限制" Margin="0,0,25,0" />
+                        <local:MySlider x:Name="SliderDownloadSpeed" Grid.Row="8" Tag="ToolDownloadSpeed" MaxValue="42" Value="42" Grid.Column="1"
                                         ToolTip="设置下载的速度上限,以避免在下载时导致其他需要联网的程序卡死" />
                                         ToolTip="设置下载的速度上限,以避免在下载时导致其他需要联网的程序卡死" />
-                        <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" Opacity="0.5" />
-                        <local:MyCheckBox Margin="0,5,0,4" Text="安装整合包时保留原始压缩包" Grid.Row="7" Height="22" Grid.ColumnSpan="2"
+                        <TextBlock VerticalAlignment="Top" Grid.Row="9" HorizontalAlignment="Left" Text="目标文件夹" Margin="0,5,25,5" />
+                        <TextBlock Margin="0,5" HorizontalAlignment="Left" Text="请在 启动 → 版本选择 → 文件夹列表 中更改下载目标文件夹。&#xa;在某个文件夹或游戏版本上右键,即可选择打开对应文件夹。" Grid.Row="9" Grid.Column="1" VerticalAlignment="Center" Opacity="0.5" />
+                        <local:MyCheckBox Margin="0,5,0,4" Text="安装整合包时保留原始压缩包" Grid.Row="10" Height="22" Grid.ColumnSpan="2"
                                           x:Name="CheckDownloadKeepModpack" Tag="ToolDownloadKeepModpack" 
                                           x:Name="CheckDownloadKeepModpack" Tag="ToolDownloadKeepModpack" 
                                           ToolTip="安装整合包时,在版本文件夹下保留用于安装的原始整合包压缩包文件" />
                                           ToolTip="安装整合包时,在版本文件夹下保留用于安装的原始整合包压缩包文件" />
-                        <local:MyCheckBox Margin="0,2,0,4" Text="简化 Mod 加载器显示" Grid.Row="8" Height="22" Grid.ColumnSpan="2"
+                        <local:MyCheckBox Margin="0,2,0,4" Text="简化 Mod 加载器显示" Grid.Row="11" Height="22" Grid.ColumnSpan="2"
                                           x:Name="CheckDownloadIgnoreQuilt" Tag="ToolDownloadIgnoreQuilt" 
                                           x:Name="CheckDownloadIgnoreQuilt" Tag="ToolDownloadIgnoreQuilt" 
                                           ToolTip="下载 Mod 时,若该 Mod 支持 Forge 和 Fabric,则显示为支持任意 Mod 加载器。&#xa;例如,开启时 JEI 显示支持全版本,关闭后 JEI 就会显示仅支持 Forge / Fabric 全版本。" />
                                           ToolTip="下载 Mod 时,若该 Mod 支持 Forge 和 Fabric,则显示为支持任意 Mod 加载器。&#xa;例如,开启时 JEI 显示支持全版本,关闭后 JEI 就会显示仅支持 Forge / Fabric 全版本。" />
-                        <local:MyCheckBox Margin="0,2,0,4" Text="验证 SSL 证书" Grid.Row="9" Height="22" Grid.ColumnSpan="2"
+                        <local:MyCheckBox Margin="0,2,0,4" Text="验证 SSL 证书" Grid.Row="12" Height="22" Grid.ColumnSpan="2"
                                           x:Name="CheckDownloadCert" Tag="ToolDownloadCert" 
                                           x:Name="CheckDownloadCert" Tag="ToolDownloadCert" 
                                           ToolTip="验证证书会提高安全性,但疑似有概率导致正版登录失败(#3018)。&#xa;如果在正版登录时因为 [根据验证过程,远程证书无效] 失败,可以尝试关闭此项。" />
                                           ToolTip="验证证书会提高安全性,但疑似有概率导致正版登录失败(#3018)。&#xa;如果在正版登录时因为 [根据验证过程,远程证书无效] 失败,可以尝试关闭此项。" />
                     </Grid>
                     </Grid>

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

@@ -56,6 +56,7 @@
         SliderDownloadSpeed.Value = Setup.Get("ToolDownloadSpeed")
         SliderDownloadSpeed.Value = Setup.Get("ToolDownloadSpeed")
         ComboDownloadVersion.SelectedIndex = Setup.Get("ToolDownloadVersion")
         ComboDownloadVersion.SelectedIndex = Setup.Get("ToolDownloadVersion")
         ComboDownloadTranslate.SelectedIndex = Setup.Get("ToolDownloadTranslate")
         ComboDownloadTranslate.SelectedIndex = Setup.Get("ToolDownloadTranslate")
+        ComboDownloadMod.SelectedIndex = Setup.Get("ToolDownloadMod")
         CheckDownloadKeepModpack.Checked = Setup.Get("ToolDownloadKeepModpack")
         CheckDownloadKeepModpack.Checked = Setup.Get("ToolDownloadKeepModpack")
         CheckDownloadIgnoreQuilt.Checked = Setup.Get("ToolDownloadIgnoreQuilt")
         CheckDownloadIgnoreQuilt.Checked = Setup.Get("ToolDownloadIgnoreQuilt")
         CheckDownloadCert.Checked = Setup.Get("ToolDownloadCert")
         CheckDownloadCert.Checked = Setup.Get("ToolDownloadCert")
@@ -90,6 +91,7 @@
             Setup.Reset("ToolDownloadKeepModpack")
             Setup.Reset("ToolDownloadKeepModpack")
             Setup.Reset("ToolDownloadIgnoreQuilt")
             Setup.Reset("ToolDownloadIgnoreQuilt")
             Setup.Reset("ToolDownloadCert")
             Setup.Reset("ToolDownloadCert")
+            Setup.Reset("ToolDownloadMod")
             Setup.Reset("ToolUpdateRelease")
             Setup.Reset("ToolUpdateRelease")
             Setup.Reset("ToolUpdateSnapshot")
             Setup.Reset("ToolUpdateSnapshot")
             Setup.Reset("ToolHelpChinese")
             Setup.Reset("ToolHelpChinese")
@@ -117,7 +119,7 @@
     Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderDebugAnim.Change, SliderDownloadThread.Change, SliderDownloadSpeed.Change
     Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderDebugAnim.Change, SliderDownloadThread.Change, SliderDownloadSpeed.Change
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value)
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value)
     End Sub
     End Sub
-    Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboDownloadVersion.SelectionChanged, ComboDownloadTranslate.SelectionChanged, ComboSystemUpdate.SelectionChanged, ComboSystemActivity.SelectionChanged
+    Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboDownloadVersion.SelectionChanged, ComboDownloadTranslate.SelectionChanged, ComboSystemUpdate.SelectionChanged, ComboSystemActivity.SelectionChanged, ComboDownloadMod.SelectionChanged
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex)
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex)
     End Sub
     End Sub
     Private Shared Sub TextBoxChange(sender As MyTextBox, e As Object) Handles TextSystemCache.ValidatedTextChanged
     Private Shared Sub TextBoxChange(sender As MyTextBox, e As Object) Handles TextSystemCache.ValidatedTextChanged