ModDownload.vb 75 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391
  1. Public Module ModDownload
  2. #Region "DlClient* | Minecraft 客户端"
  3. ''' <summary>
  4. ''' 返回某 Minecraft 版本对应的原版主 Jar 文件的下载信息,要求对应依赖版本已存在。
  5. ''' 失败则抛出异常,不需要下载则返回 Nothing。
  6. ''' </summary>
  7. Public Function DlClientJarGet(Instance As McInstance, ReturnNothingOnFileUseable As Boolean) As NetFile
  8. '获取底层继承版本
  9. Try
  10. Do While Not String.IsNullOrEmpty(Instance.InheritName)
  11. Instance = New McInstance(Instance.InheritName)
  12. Loop
  13. Catch ex As Exception
  14. Logger.Warn(ex, "获取底层继承版本失败")
  15. End Try
  16. '检查 Json 是否标准
  17. If Instance.JsonObject("downloads") Is Nothing OrElse Instance.JsonObject("downloads")("client") Is Nothing OrElse Instance.JsonObject("downloads")("client")("url") Is Nothing Then
  18. Throw New Exception("底层版本 " & Instance.Name & " 中无 jar 文件下载信息")
  19. End If
  20. '检查文件
  21. Dim Checker As New FileChecker With {.MinSize = 1024, .ActualSize = If(Instance.JsonObject("downloads")("client")("size"), -1), .Hash = Instance.JsonObject("downloads")("client")("sha1")}
  22. If ReturnNothingOnFileUseable AndAlso Checker.Check(Instance.PathVersion & Instance.Name & ".jar") Is Nothing Then Return Nothing '通过校验
  23. '返回下载信息
  24. Dim JarUrl As String = Instance.JsonObject("downloads")("client")("url")
  25. Return New NetFile(DlSourceLauncherOrMetaGet(JarUrl), Instance.PathVersion & Instance.Name & ".jar", Checker)
  26. End Function
  27. ''' <summary>
  28. ''' 返回某 Minecraft 版本对应的原版主 AssetIndex 文件的下载信息,要求对应依赖版本已存在。
  29. ''' 若未找到,则会返回 Legacy 资源文件或 Nothing。
  30. ''' </summary>
  31. Public Function DlClientAssetIndexGet(Instance As McInstance) As NetFile
  32. '获取底层继承版本
  33. Do While Not String.IsNullOrEmpty(Instance.InheritName)
  34. Instance = New McInstance(Instance.InheritName)
  35. Loop
  36. '获取信息
  37. Dim IndexInfo = McAssetsGetIndex(Instance, True, True)
  38. Dim IndexAddress As String = McFolderSelected & "assets\indexes\" & IndexInfo("id").ToString & ".json"
  39. Logger.Info($"版本 {Instance.Name} 对应的资源文件索引为 {IndexInfo("id")}")
  40. Dim IndexUrl As String = If(IndexInfo("url"), "")
  41. If IndexUrl = "" Then
  42. Return Nothing
  43. Else
  44. Return New NetFile(DlSourceLauncherOrMetaGet(IndexUrl), IndexAddress, New FileChecker With {.IsJson = True})
  45. End If
  46. End Function
  47. ''' <summary>
  48. ''' 构造补全某 Minecraft 版本的所有文件的加载器列表。失败会抛出异常。
  49. ''' </summary>
  50. Public Function DlClientFix(Instance As McInstance, CheckAssetsHash As Boolean, DownloadAssetIndexInBackground As Boolean) As List(Of LoaderBase)
  51. Dim Loaders As New List(Of LoaderBase)
  52. #Region "下载支持库文件"
  53. Dim LoadersLib As New List(Of LoaderBase) From {
  54. New LoaderTask(Of String, List(Of NetFile))("分析缺失支持库文件", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibNetFilesFromInstance(Instance)) With {.ProgressWeight = 1},
  55. New LoaderDownload("下载支持库文件", New List(Of NetFile)) With {.ProgressWeight = 15}
  56. }
  57. '构造加载器
  58. Loaders.Add(New LoaderCombo(Of String)("下载支持库文件(主加载器)", LoadersLib) With {.Block = False, .Show = False, .ProgressWeight = 16})
  59. #End Region
  60. #Region "下载资源文件"
  61. If ShouldIgnoreFileCheck(Instance) Then
  62. Logger.Warn("设置要求跳过所有 Assets 检查,文件可能不是最新的", LogBehavior.None)
  63. Else
  64. Dim LoadersAssets As New List(Of LoaderBase)
  65. '获取资源文件索引地址
  66. Dim TempAddressMain As String = Nothing
  67. Dim RealAddressMain As String = Nothing
  68. LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析资源文件索引地址",
  69. Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  70. Try
  71. Dim IndexFile = DlClientAssetIndexGet(Instance)
  72. If IndexFile Is Nothing Then
  73. Task.Output = New List(Of NetFile)
  74. Logger.Warn($"未找到版本 {Instance.Name} 的合适的资源索引下载地址,游戏 assets 可能缺失")
  75. Return
  76. End If
  77. Dim IndexBackgroundFile = PathTemp & "Cache\" & IndexFile.LocalName & "_background"
  78. If FileUtils.Exists(IndexBackgroundFile) AndAlso
  79. FileUtils.GetInfo(IndexBackgroundFile).LastWriteTime.Date > FileUtils.GetInfo(IndexFile.LocalPath).LastWriteTime.Date Then
  80. Logger.Info($"发现后台更新的资源索引缓存文件:{IndexBackgroundFile}")
  81. FileUtils.Move(IndexBackgroundFile, IndexFile.LocalPath)
  82. End If
  83. If DownloadAssetIndexInBackground Then
  84. '允许后台更新索引
  85. If IndexFile.Check.Check(IndexFile.LocalPath) Is Nothing Then
  86. Task.Output = New List(Of NetFile)
  87. Else
  88. Task.Output = New List(Of NetFile) From {IndexFile}
  89. End If
  90. Else
  91. '强制要求重新下载
  92. RealAddressMain = IndexFile.LocalPath
  93. TempAddressMain = PathTemp & "Cache\" & IndexFile.LocalName
  94. IndexFile.LocalPath = TempAddressMain
  95. Task.Output = New List(Of NetFile) From {IndexFile}
  96. End If
  97. Catch ex As Exception
  98. Throw New Exception("分析资源文件索引地址失败", ex)
  99. End Try
  100. End Sub) With {.ProgressWeight = 0.5, .Show = False})
  101. '下载资源文件索引
  102. LoadersAssets.Add(New LoaderDownload("下载资源文件索引", New List(Of NetFile)) With {.ProgressWeight = 2})
  103. '获取资源文件地址
  104. LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("获取资源文件列表",
  105. Sub(Task)
  106. If TempAddressMain IsNot Nothing Then FileUtils.Move(TempAddressMain, RealAddressMain)
  107. Task.Output = McAssetsFixList(Instance, CheckAssetsHash, Task)
  108. End Sub) With {.ProgressWeight = 0.01})
  109. '下载资源文件
  110. LoadersAssets.Add(New LoaderDownload("下载资源文件", New List(Of NetFile)) With {.ProgressWeight = 25})
  111. '构造加载器
  112. Loaders.Add(New LoaderCombo(Of String)("下载资源文件(主加载器)", LoadersAssets) With {.Block = False, .Show = False, .ProgressWeight = 27})
  113. '后台独立更新索引
  114. If DownloadAssetIndexInBackground Then
  115. Dim RealAddressBg As String = Nothing
  116. Dim TempAddressBg As String = Nothing
  117. Dim LoadersAssetsUpdate As New List(Of LoaderBase)
  118. LoadersAssetsUpdate.Add(New LoaderTask(Of String, List(Of NetFile))("后台分析资源文件索引地址",
  119. Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  120. Dim IndexFile As NetFile = DlClientAssetIndexGet(Instance)
  121. If IndexFile Is Nothing Then
  122. Logger.Warn($"未找到版本 {Instance.Name} 的合适的资源索引下载地址,游戏 assets 可能缺失")
  123. Task.Cancel()
  124. Throw New OperationCanceledException
  125. End If
  126. RealAddressBg = IndexFile.LocalPath
  127. TempAddressBg = PathTemp & "Cache\" & IndexFile.LocalName & "_background"
  128. IndexFile.LocalPath = TempAddressBg
  129. Task.Output = New List(Of NetFile) From {IndexFile}
  130. '检查是否需要更新:每天只更新一次
  131. Dim LastUpdate As Date = FileUtils.GetInfo(RealAddressBg).LastWriteTime.Date
  132. If FileUtils.Exists(RealAddressBg) AndAlso Math.Abs((LastUpdate - Now.Date).TotalDays) < 1 Then
  133. Logger.Info($"无需更新资源文件索引,上次更新于 {LastUpdate}")
  134. Task.Cancel()
  135. End If
  136. End Sub))
  137. LoadersAssetsUpdate.Add(New LoaderDownload("后台下载资源文件索引", New List(Of NetFile)))
  138. Dim Updater As New LoaderCombo(Of String)("后台更新资源文件索引", LoadersAssetsUpdate)
  139. Logger.Info("开始后台检查资源文件索引")
  140. Updater.Start()
  141. End If
  142. End If
  143. #End Region
  144. Return Loaders
  145. End Function
  146. #End Region
  147. #Region "DlClientList | Minecraft 客户端 版本列表"
  148. ''' <summary>
  149. ''' 所有正式版的 Minecraft Drop 序数。
  150. ''' 若从未完成过获取,返回 Nothing;否则必定存在元素,且从高到低排列。
  151. ''' </summary>
  152. Public Property AllDrops As List(Of Integer)
  153. Get
  154. If _AllDrops Is Nothing Then
  155. _AllDrops = Settings.Get(Of String)("CacheDrops").ToString.
  156. Split(",", True).Select(Function(d) CInt(Val(d))).ToList()
  157. End If
  158. Return If(_AllDrops.Any, _AllDrops, Nothing) '不要将 _AllDrops 再设为 Nothing,以防止反复获取设置尝试初始化
  159. End Get
  160. Set(value As List(Of Integer))
  161. _AllDrops = value
  162. Settings.Set("CacheDrops", value.Join(","c))
  163. End Set
  164. End Property
  165. Private _AllDrops As List(Of Integer) = Nothing
  166. '主加载器
  167. Public Structure DlClientListResult
  168. ''' <summary>
  169. ''' 数据来源名称,如“Mojang”,“BMCLAPI”。
  170. ''' </summary>
  171. Public SourceName As String
  172. ''' <summary>
  173. ''' 是否为官方的实时数据。
  174. ''' </summary>
  175. Public IsOfficial As Boolean
  176. ''' <summary>
  177. ''' 获取到的 Json 数据。
  178. ''' </summary>
  179. Public Value As JObject
  180. End Structure
  181. ''' <summary>
  182. ''' Minecraft 客户端 版本列表,主加载器。
  183. ''' 若要求镜像源必须包含某个版本,则将该版本 ID 作为输入(#5195)。
  184. ''' </summary>
  185. Public DlClientListLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Main", AddressOf DlClientListMain)
  186. Private Sub DlClientListMain(Loader As LoaderTask(Of String, DlClientListResult))
  187. Select Case Settings.Get(Of Integer)("ToolDownloadVersion")
  188. Case 0
  189. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)) From {
  190. New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListBmclapiLoader, 30),
  191. New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListMojangLoader, 30 + 60)
  192. }, Loader.IsForceRestarting)
  193. Case 1
  194. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)) From {
  195. New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListMojangLoader, 5),
  196. New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListBmclapiLoader, 5 + 30)
  197. }, Loader.IsForceRestarting)
  198. Case Else
  199. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)) From {
  200. New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListMojangLoader, 60),
  201. New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListBmclapiLoader, 60 + 60)
  202. }, Loader.IsForceRestarting)
  203. End Select
  204. '提取所有 Drop 序数
  205. Dim Drops As New List(Of Integer)
  206. For Each Version As JObject In Loader.Output.Value("versions")
  207. Dim Id As String = Version("id").ToString
  208. If Id.Contains("-") Then Continue For
  209. Dim Drop As Integer = McVersion.VersionToDrop(Id)
  210. If Drop = 209 Then Continue For
  211. Drops.Add(Drop)
  212. Next
  213. AllDrops = Drops.Distinct.OrderByDescending(Function(d) d).ToList()
  214. End Sub
  215. '各个下载源的分加载器
  216. ''' <summary>
  217. ''' Minecraft 客户端 版本列表,Mojang 官方源加载器。
  218. ''' </summary>
  219. Public DlClientListMojangLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Mojang", AddressOf DlClientListMojangMain)
  220. Private Sub DlClientListMojangMain(Loader As LoaderTask(Of String, DlClientListResult))
  221. Dim StartTime As Long = GetTimeMs()
  222. Dim Json As JObject = NetRequestByClientRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json", RequireJson:=True).DeserializeJson()
  223. Try
  224. Dim Versions As JArray = Json("versions")
  225. If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
  226. '确定官方源是否可用
  227. If Not DlPreferMojang Then
  228. Dim DeltaTime = GetTimeMs() - StartTime
  229. DlPreferMojang = DeltaTime < 4000
  230. Logger.Info($"Mojang 官方源加载耗时:{DeltaTime}ms,{If(DlPreferMojang, "可优先使用官方源", "不优先使用官方源")}")
  231. End If
  232. '添加 PCL 特供项
  233. If FileUtils.Exists(PathTemp & "Cache\download.json") Then Versions.Merge(FileUtils.ReadAsJson(PathTemp & "Cache\download.json"))
  234. '返回
  235. Loader.Output = New DlClientListResult With {.IsOfficial = True, .SourceName = "Mojang 官方源", .Value = Json}
  236. 'MC 更新提示
  237. Static IsHinted As Boolean = False
  238. Dim Version As String
  239. '快照版
  240. Version = Json("latest")("snapshot")
  241. If Settings.Get(Of Boolean)("ToolUpdateSnapshot") AndAlso Not Settings.Get(Of String)("ToolUpdateSnapshotLast") = "" AndAlso
  242. Settings.Get(Of String)("ToolUpdateSnapshotLast") <> Version AndAlso Not IsHinted Then
  243. IsHinted = True
  244. McDownloadClientUpdateHint(Version, Json)
  245. End If
  246. Settings.Set("ToolUpdateSnapshotLast", If(Version, "Nothing"))
  247. '正式版
  248. Version = Json("latest")("release")
  249. If Settings.Get(Of Boolean)("ToolUpdateRelease") AndAlso Not Settings.Get(Of String)("ToolUpdateReleaseLast") = "" AndAlso
  250. Settings.Get(Of String)("ToolUpdateReleaseLast") <> Version AndAlso Not IsHinted Then
  251. IsHinted = True
  252. McDownloadClientUpdateHint(Version, Json)
  253. End If
  254. Settings.Set("ToolUpdateReleaseLast", Version)
  255. Catch ex As Exception
  256. Throw New Exception("Minecraft 官方源版本列表解析失败", ex)
  257. End Try
  258. End Sub
  259. ''' <summary>
  260. ''' Minecraft 客户端 版本列表,BMCLAPI 源加载器。
  261. ''' </summary>
  262. Public DlClientListBmclapiLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Bmclapi", AddressOf DlClientListBmclapiMain)
  263. Private Sub DlClientListBmclapiMain(Loader As LoaderTask(Of String, DlClientListResult))
  264. Dim Json As JObject = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json", RequireJson:=True).DeserializeJson()
  265. Try
  266. Dim Versions As JArray = Json("versions")
  267. If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
  268. '添加 PCL 特供项
  269. If FileUtils.Exists(PathTemp & "Cache\download.json") Then Versions.Merge(FileUtils.ReadAsJson(PathTemp & "Cache\download.json"))
  270. '检查是否有要求的版本(#5195)
  271. If Not String.IsNullOrEmpty(Loader.Input) Then
  272. Dim Id = Loader.Input
  273. If DlClientListLoader.Output.Value IsNot Nothing AndAlso Not DlClientListLoader.Output.Value("versions").Any(Function(v) v("id") = Id) Then
  274. Throw New Exception("BMCLAPI 源未包含目标版本 " & Id)
  275. End If
  276. End If
  277. '返回
  278. Loader.Output = New DlClientListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Json}
  279. Catch ex As Exception
  280. Throw New Exception("Minecraft BMCLAPI 版本列表解析失败(" & Json.ToString & ")", ex)
  281. End Try
  282. End Sub
  283. ''' <summary>
  284. ''' 获取某个版本的 Json 下载地址,若失败则返回 Nothing。必须在工作线程执行。
  285. ''' </summary>
  286. Public Function DlClientListGet(Id As String)
  287. Try
  288. '确认版本格式标准
  289. Id = Id.Replace("_", "-") '1.7.10_pre4 在版本列表中显示为 1.7.10-pre4
  290. If Id <> "1.0" AndAlso Id.EndsWithF(".0") Then Id = Left(Id, Id.Length - 2) 'OptiFine 1.8 的下载会触发此问题,显示版本为 1.8.0
  291. '获取 Minecraft 版本列表
  292. Select Case DlClientListLoader.State
  293. Case LoadState.Finished
  294. '从当前的结果获取目标版本…
  295. For Each Version As JObject In DlClientListLoader.Output.Value("versions")
  296. If Version("id") = Id Then Return Version("url").ToString
  297. Next
  298. '…如果没有,则重新尝试获取(在版本刚更新时可能出现这种情况,#5195)
  299. DlClientListLoader.WaitForExit(Id, IsForceRestart:=True)
  300. Case LoadState.Loading
  301. DlClientListLoader.WaitForExit(Id)
  302. Case LoadState.Failed, LoadState.Canceled, LoadState.Waiting
  303. DlClientListLoader.WaitForExit(Id, IsForceRestart:=True)
  304. End Select
  305. '重新查找版本
  306. For Each Version As JObject In DlClientListLoader.Output.Value("versions")
  307. If Version("id") = Id Then Return Version("url").ToString
  308. Next
  309. Logger.Warn($"未发现版本 {Id} 的 json 下载地址,版本列表返回为:{vbCrLf}{DlClientListLoader.Output.Value}")
  310. Return Nothing
  311. Catch ex As Exception
  312. Logger.Warn(ex, $"获取版本 {Id} 的 json 下载地址失败")
  313. Return Nothing
  314. End Try
  315. End Function
  316. #End Region
  317. #Region "DlOptiFineList | OptiFine 版本列表"
  318. Public Structure DlOptiFineListResult
  319. ''' <summary>
  320. ''' 数据来源名称,如“Official”,“BMCLAPI”。
  321. ''' </summary>
  322. Public SourceName As String
  323. ''' <summary>
  324. ''' 是否为官方的实时数据。
  325. ''' </summary>
  326. Public IsOfficial As Boolean
  327. ''' <summary>
  328. ''' 获取到的数据。
  329. ''' </summary>
  330. Public Value As List(Of DlOptiFineListEntry)
  331. End Structure
  332. Public Class DlOptiFineListEntry
  333. ''' <summary>
  334. ''' 显示名称,已去除 HD_U 字样,如“1.12.2 C8”。
  335. ''' </summary>
  336. Public DisplayName As String
  337. ''' <summary>
  338. ''' 原始文件名称,如“preview_OptiFine_1.11_HD_U_E1_pre.jar”。
  339. ''' </summary>
  340. Public FileName As String
  341. ''' <summary>
  342. ''' 对应的版本名称,如“1.13.2-OptiFine_HD_U_E6”。
  343. ''' </summary>
  344. Public InstanceName As String
  345. ''' <summary>
  346. ''' 是否为测试版。
  347. ''' </summary>
  348. Public IsPreview As Boolean
  349. ''' <summary>
  350. ''' 对应的 Minecraft 版本,如“1.12.2”。
  351. ''' </summary>
  352. Public Property Inherit As String
  353. Get
  354. Return _inherit
  355. End Get
  356. Set(value As String)
  357. If value.EndsWithF(".0") Then value = Left(value, value.Length - 2)
  358. _inherit = value
  359. End Set
  360. End Property
  361. Private _inherit As String
  362. ''' <summary>
  363. ''' 发布时间,格式为“yyyy/mm/dd”。OptiFine 源无此数据。
  364. ''' </summary>
  365. Public ReleaseTime As String
  366. ''' <summary>
  367. ''' 需要的最低 Forge 版本。空字符串为无限制,Nothing 为不兼容,“28.1.56” 表示版本号,“1161” 表示版本号的最后一位。
  368. ''' </summary>
  369. Public RequiredForgeVersion As String
  370. End Class
  371. ''' <summary>
  372. ''' OptiFine 版本列表,主加载器。
  373. ''' </summary>
  374. Public DlOptiFineListLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Main", AddressOf DlOptiFineListMain)
  375. Private Sub DlOptiFineListMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
  376. Select Case Settings.Get(Of Integer)("ToolDownloadVersion")
  377. Case 0
  378. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)) From {
  379. New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListBmclapiLoader, 30),
  380. New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListOfficialLoader, 30 + 60)
  381. }, Loader.IsForceRestarting)
  382. Case 1
  383. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)) From {
  384. New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListOfficialLoader, 5),
  385. New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListBmclapiLoader, 5 + 30)
  386. }, Loader.IsForceRestarting)
  387. Case Else
  388. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)) From {
  389. New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListOfficialLoader, 60),
  390. New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListBmclapiLoader, 60 + 60)
  391. }, Loader.IsForceRestarting)
  392. End Select
  393. End Sub
  394. ''' <summary>
  395. ''' OptiFine 版本列表,官方源。
  396. ''' </summary>
  397. Public DlOptiFineListOfficialLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Official", AddressOf DlOptiFineListOfficialMain)
  398. Private Sub DlOptiFineListOfficialMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
  399. Dim Result As String = NetRequestByClientRetry("https://optifine.net/downloads", Encoding:=Encoding.Default)
  400. If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
  401. Try
  402. '获取所有版本信息
  403. Dim Forge As List(Of String) = Result.RegexSearch("(?<=colForge'>)[^<]*").ToList
  404. Dim ReleaseTime As List(Of String) = Result.RegexSearch("(?<=colDate'>)[^<]+").ToList
  405. Dim Name As List(Of String) = Result.RegexSearch("(?<=OptiFine_)[0-9A-Za-z_.]+(?=.jar"")").ToList
  406. If Not ReleaseTime.Count = Name.Count Then Throw New Exception("版本与发布时间数据无法对应")
  407. If Not Forge.Count = Name.Count Then Throw New Exception("版本与 Forge 兼容数据无法对应")
  408. If ReleaseTime.Count < 10 Then Throw New Exception("获取到的版本数量不足(" & Result & ")")
  409. '转化为列表输出
  410. Dim Versions As New List(Of DlOptiFineListEntry)
  411. For i = 0 To ReleaseTime.Count - 1
  412. Name(i) = Name(i).Replace("_", " ")
  413. Dim Entry As New DlOptiFineListEntry With {
  414. .DisplayName = Name(i).Replace("HD U ", "").Replace(".0 ", " "),
  415. .ReleaseTime = {ReleaseTime(i).Split(".")(2), ReleaseTime(i).Split(".")(1), ReleaseTime(i).Split(".")(0)}.Join("/"c),
  416. .IsPreview = Name(i).ContainsIgnoreCase("pre"),
  417. .Inherit = Name(i).ToString.Split(" ")(0),
  418. .FileName = If(Name(i).ContainsIgnoreCase("pre"), "preview_", "") & "OptiFine_" & Name(i).Replace(" ", "_") & ".jar",
  419. .RequiredForgeVersion = Forge(i).Replace("Forge ", "").Replace("#", "")}
  420. If Entry.RequiredForgeVersion.Contains("N/A") Then Entry.RequiredForgeVersion = Nothing
  421. Entry.InstanceName = Entry.Inherit & "-OptiFine_" & Name(i).ToString.Replace(" ", "_").Replace(Entry.Inherit & "_", "")
  422. Versions.Add(Entry)
  423. Next
  424. Loader.Output = New DlOptiFineListResult With {.IsOfficial = True, .SourceName = "OptiFine 官方源", .Value = Versions}
  425. Catch ex As Exception
  426. Throw New Exception("OptiFine 官方源版本列表解析失败(" & Result & ")", ex)
  427. End Try
  428. End Sub
  429. ''' <summary>
  430. ''' OptiFine 版本列表,BMCLAPI。
  431. ''' </summary>
  432. Public DlOptiFineListBmclapiLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Bmclapi", AddressOf DlOptiFineListBmclapiMain)
  433. Private Sub DlOptiFineListBmclapiMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
  434. Dim Json As JArray = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/optifine/versionList", RequireJson:=True).DeserializeJson()
  435. Try
  436. Dim Versions As New List(Of DlOptiFineListEntry)
  437. For Each Token As JObject In Json
  438. Dim Entry As New DlOptiFineListEntry With {
  439. .DisplayName = (Token("mcversion").ToString & Token("type").ToString.Replace("HD_U", "").Replace("_", " ") & " " & Token("patch").ToString).Replace(".0 ", " "),
  440. .ReleaseTime = "",
  441. .IsPreview = Token("patch").ToString.ContainsIgnoreCase("pre"),
  442. .Inherit = Token("mcversion").ToString,
  443. .FileName = Token("filename").ToString,
  444. .RequiredForgeVersion = If(Token("forge"), "").ToString.Replace("Forge ", "").Replace("#", "")
  445. }
  446. If Entry.RequiredForgeVersion.Contains("N/A") Then Entry.RequiredForgeVersion = Nothing
  447. Entry.InstanceName = Entry.Inherit & "-OptiFine_" & (Token("type").ToString & " " & Token("patch").ToString).Replace(".0 ", " ").Replace(" ", "_").Replace(Entry.Inherit & "_", "")
  448. Versions.Add(Entry)
  449. Next
  450. Loader.Output = New DlOptiFineListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Versions}
  451. Catch ex As Exception
  452. Throw New Exception("OptiFine BMCLAPI 版本列表解析失败(" & Json.ToString & ")", ex)
  453. End Try
  454. End Sub
  455. #End Region
  456. #Region "DlForgeList | Forge Minecraft 版本列表"
  457. Public Structure DlForgeListResult
  458. ''' <summary>
  459. ''' 数据来源名称,如“Official”,“BMCLAPI”。
  460. ''' </summary>
  461. Public SourceName As String
  462. ''' <summary>
  463. ''' 是否为官方的实时数据。
  464. ''' </summary>
  465. Public IsOfficial As Boolean
  466. ''' <summary>
  467. ''' 获取到的数据。
  468. ''' </summary>
  469. Public Value As List(Of String)
  470. End Structure
  471. ''' <summary>
  472. ''' Forge 版本列表,主加载器。
  473. ''' </summary>
  474. Public DlForgeListLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Main", AddressOf DlForgeListMain)
  475. Private Sub DlForgeListMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
  476. Select Case Settings.Get(Of Integer)("ToolDownloadVersion")
  477. Case 0
  478. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)) From {
  479. New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListBmclapiLoader, 30),
  480. New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListOfficialLoader, 30 + 60)
  481. }, Loader.IsForceRestarting)
  482. Case 1
  483. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)) From {
  484. New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListOfficialLoader, 5),
  485. New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListBmclapiLoader, 5 + 30)
  486. }, Loader.IsForceRestarting)
  487. Case Else
  488. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)) From {
  489. New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListOfficialLoader, 60),
  490. New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListBmclapiLoader, 60 + 60)
  491. }, Loader.IsForceRestarting)
  492. End Select
  493. End Sub
  494. ''' <summary>
  495. ''' Forge 版本列表,官方源。
  496. ''' </summary>
  497. Public DlForgeListOfficialLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Official", AddressOf DlForgeListOfficialMain)
  498. Private Sub DlForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
  499. Dim Result As String = NetRequestByClientRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding:=Encoding.Default, Accept:="text/html", SimulateBrowserHeaders:=True)
  500. If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
  501. '获取所有版本信息
  502. Dim Names As List(Of String) = Result.RegexSearch("(?<=a href=""index_)[0-9.]+((_pre|[_-]snapshot[_-]?)[0-9]?)?(?=.html)").ToList
  503. Names.Add("1.2.4") '1.2.4 不会被匹配上
  504. If Names.Count < 10 Then Throw New Exception("获取到的版本数量不足(" & Result & ")")
  505. Loader.Output = New DlForgeListResult With {.IsOfficial = True, .SourceName = "Forge 官方源", .Value = Names}
  506. End Sub
  507. ''' <summary>
  508. ''' Forge 版本列表,BMCLAPI。
  509. ''' </summary>
  510. Public DlForgeListBmclapiLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Bmclapi", AddressOf DlForgeListBmclapiMain)
  511. Private Sub DlForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
  512. Dim Result As JArray = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft",
  513. Encoding:=Encoding.Default, RequireJson:=True).DeserializeJson()
  514. Dim Names As List(Of String) = Result.Select(Function(v) v.ToString).ToList
  515. If Names.Count < 10 Then Throw New Exception("获取到的版本数量不足")
  516. Loader.Output = New DlForgeListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Names}
  517. End Sub
  518. #End Region
  519. #Region "DlForgeVersion | Forge 版本列表"
  520. Public MustInherit Class DlForgelikeEntry
  521. Implements IComparable(Of DlForgelikeEntry)
  522. Public IsNeoForge As Boolean
  523. ''' <summary>
  524. ''' 加载器名称。Forge 或 NeoForge。
  525. ''' </summary>
  526. Public MustOverride ReadOnly Property LoaderName As String
  527. ''' <summary>
  528. ''' 文件扩展名。不以小数点开头。
  529. ''' </summary>
  530. Public MustOverride ReadOnly Property FileExtension As String
  531. ''' <summary>
  532. ''' 标准化后的版本号,仅可用于比较与排序,且 NeoForge 项不包含 alpha/beta 编号!
  533. ''' 格式:Major.Minor.Build.Revision
  534. ''' Forge:如 “50.1.9.0”(最后一位固定为 0)、“14.22.1.2478”(Legacy)。
  535. ''' NeoForge:如 “20.4.30.0”(最后一位固定为 0)、“19.47.1.99”(Legacy:第一位固定为 19)。
  536. ''' </summary>
  537. Public Version As Version
  538. ''' <summary>
  539. ''' 可读的非格式化版本名。
  540. ''' Forge:如 “50.1.9”、“14.22.1.2478”(Legacy)。
  541. ''' NeoForge:如 “20.4.30-beta”、“47.1.99”(Legacy)。
  542. ''' </summary>
  543. Public VersionName As String
  544. ''' <summary>
  545. ''' 对应的 Minecraft 版本,如 1.12.2、26.1-snapshot-1。
  546. ''' </summary>
  547. Public Inherit As String
  548. Public Function CompareTo(other As DlForgelikeEntry) As Integer Implements IComparable(Of DlForgelikeEntry).CompareTo
  549. If Version <> other.Version Then
  550. Return Version.CompareTo(other.Version)
  551. Else
  552. Return CompareVersion(VersionName, other.VersionName)
  553. End If
  554. End Function
  555. End Class
  556. Public Class DlForgeVersionEntry
  557. Inherits DlForgelikeEntry
  558. ''' <summary>
  559. ''' 发布时间,格式为“yyyy/MM/dd HH:mm”。
  560. ''' </summary>
  561. Public ReleaseTime As String
  562. ''' <summary>
  563. ''' 文件的 MD5 或 SHA1(BMCLAPI 的老版本是 MD5,新版本是 SHA1;官方源总是 MD5)。
  564. ''' </summary>
  565. Public Hash As String = Nothing
  566. ''' <summary>
  567. ''' 是否为推荐版本。
  568. ''' </summary>
  569. Public IsRecommended As Boolean
  570. ''' <summary>
  571. ''' 安装类型。有 installer、client、universal 三种。
  572. ''' </summary>
  573. Public Category As String
  574. ''' <summary>
  575. ''' 用于下载的文件版本名。可能在 Version 的基础上添加了分支。
  576. ''' </summary>
  577. Public FileVersion As String
  578. ''' <summary>
  579. ''' 加载器名称。Forge 或 NeoForge。
  580. ''' </summary>
  581. Public Overrides ReadOnly Property LoaderName As String
  582. Get
  583. Return "Forge"
  584. End Get
  585. End Property
  586. ''' <summary>
  587. ''' 文件扩展名。不以小数点开头。
  588. ''' </summary>
  589. Public Overrides ReadOnly Property FileExtension As String
  590. Get
  591. Return If(Category = "installer", "jar", "zip")
  592. End Get
  593. End Property
  594. Public Sub New(Version As String, Branch As String, Inherit As String)
  595. '司马版本的特殊处理
  596. If Version = "11.15.1.2318" OrElse Version = "11.15.1.1902" OrElse Version = "11.15.1.1890" Then Branch = "1.8.9"
  597. If Branch Is Nothing AndAlso Inherit = "1.7.10" AndAlso Version.Split(".")(3) >= 1300 Then Branch = "1.7.10"
  598. '为 DlForgelikeEntry 提供所有信息
  599. IsNeoForge = False
  600. VersionName = Version
  601. Me.Version = New Version(Version)
  602. Me.Inherit = Inherit
  603. FileVersion = Version & If(Branch Is Nothing, "", "-" & Branch)
  604. End Sub
  605. End Class
  606. ''' <summary>
  607. ''' Forge 版本列表,主加载器。
  608. ''' </summary>
  609. Public Sub DlForgeVersionMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
  610. Dim DlForgeVersionOfficialLoader As New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion Official", AddressOf DlForgeVersionOfficialMain)
  611. Dim DlForgeVersionBmclapiLoader As New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion Bmclapi", AddressOf DlForgeVersionBmclapiMain)
  612. Select Case Settings.Get(Of Integer)("ToolDownloadVersion")
  613. Case 0
  614. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)) From {
  615. New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionBmclapiLoader, 30),
  616. New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionOfficialLoader, 30 + 60)
  617. }, Loader.IsForceRestarting)
  618. Case 1
  619. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)) From {
  620. New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionOfficialLoader, 5),
  621. New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionBmclapiLoader, 5 + 30)
  622. }, Loader.IsForceRestarting)
  623. Case Else
  624. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)) From {
  625. New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionOfficialLoader, 60),
  626. New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionBmclapiLoader, 60 + 60)
  627. }, Loader.IsForceRestarting)
  628. End Select
  629. End Sub
  630. ''' <summary>
  631. ''' Forge 版本列表,官方源。
  632. ''' </summary>
  633. Public Sub DlForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
  634. Dim Result As String
  635. Try
  636. Result = NetRequestByLoader("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" &
  637. Loader.Input.Replace("-", "_") & '兼容 Forge 1.7.10-pre4,#4057
  638. ".html", SimulateBrowserHeaders:=True)
  639. Catch ex As Exception
  640. If ex.GetDisplay(False).Contains("(404)") Then
  641. Throw New Exception("无")
  642. Else
  643. Throw
  644. End If
  645. End Try
  646. If Result.Length < 1000 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
  647. Dim Versions As New List(Of DlForgeVersionEntry)
  648. Try
  649. '分割版本信息
  650. Dim VersionCodes = Result.Substring(0, Result.LastIndexOfF("</table>")).Split("<td class=""download-version")
  651. '获取所有版本信息
  652. For i = 1 To VersionCodes.Count - 1
  653. Dim VersionCode = VersionCodes(i)
  654. Try
  655. '基础信息获取
  656. Dim Name As String = VersionCode.RegexSeek("(?<=[^(0-9)]+)[0-9\.]+")
  657. Dim IsRecommended As Boolean = VersionCode.Contains("fa promo-recommended")
  658. Dim Inherit As String = Loader.Input
  659. '分支获取
  660. Dim Branch As String = VersionCode.RegexSeek($"(?<=-{Name}-)[^-""]+(?=-[a-z]+.[a-z]{{3}})")
  661. If String.IsNullOrWhiteSpace(Branch) Then Branch = Nothing
  662. '发布时间获取
  663. Dim ReleaseTimeOriginal = VersionCode.RegexSeek("(?<=""download-time"" title="")[^""]+")
  664. Dim ReleaseTimeSplit = ReleaseTimeOriginal.Split(" -:".ToCharArray) '原格式:"2021-02-15 03:24:02"
  665. Dim ReleaseDate As New Date(ReleaseTimeSplit(0), ReleaseTimeSplit(1), ReleaseTimeSplit(2), '年月日
  666. ReleaseTimeSplit(3), ReleaseTimeSplit(4), ReleaseTimeSplit(5), '时分秒
  667. 0, DateTimeKind.Utc) '以 UTC 时间作为标准
  668. Dim ReleaseTime As String = ReleaseDate.ToLocalTime.ToString("yyyy'/'MM'/'dd HH':'mm") '时区与格式转换
  669. '分类与 MD5 获取
  670. Dim MD5 As String, Category As String
  671. If VersionCode.Contains("classifier-installer""") Then
  672. '类型为 installer.jar,支持范围 ~753 (~ 1.6.1 部分), 738~684 (1.5.2 全部)
  673. VersionCode = VersionCode.Substring(VersionCode.IndexOfF("installer.jar"))
  674. MD5 = VersionCode.RegexSeek("(?<=MD5:</strong> )[^<]+")
  675. Category = "installer"
  676. ElseIf VersionCode.Contains("classifier-universal""") Then
  677. '类型为 universal.zip,支持范围 751~449 (1.6.1 部分), 682~183 (1.5.1 ~ 1.3.2 部分)
  678. VersionCode = VersionCode.Substring(VersionCode.IndexOfF("universal.zip"))
  679. MD5 = VersionCode.RegexSeek("(?<=MD5:</strong> )[^<]+")
  680. Category = "universal"
  681. ElseIf VersionCode.Contains("client.zip") Then
  682. '类型为 client.zip,支持范围 182~ (1.3.2 部分 ~)
  683. VersionCode = VersionCode.Substring(VersionCode.IndexOfF("client.zip"))
  684. MD5 = VersionCode.RegexSeek("(?<=MD5:</strong> )[^<]+")
  685. Category = "client"
  686. Else
  687. '没有任何下载(1.6.4 有一部分这种情况)
  688. Continue For
  689. End If
  690. '添加进列表
  691. Versions.Add(New DlForgeVersionEntry(Name, Branch, Inherit) With {.Category = Category, .IsRecommended = IsRecommended, .Hash = MD5.ReplaceLineEndings(""), .ReleaseTime = ReleaseTime})
  692. Catch ex As Exception
  693. Throw New Exception("Forge 官方源版本信息提取失败(" & VersionCode & ")", ex)
  694. End Try
  695. Next
  696. Catch ex As Exception
  697. Throw New Exception("Forge 官方源版本列表解析失败(" & Result & ")", ex)
  698. End Try
  699. If Not Versions.Any() Then Throw New Exception("无")
  700. Loader.Output = Versions
  701. End Sub
  702. ''' <summary>
  703. ''' Forge 版本列表,BMCLAPI。
  704. ''' </summary>
  705. Public Sub DlForgeVersionBmclapiMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
  706. Dim Json As JArray = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" &
  707. Loader.Input.Replace("-", "_"), RequireJson:=True).DeserializeJson() '兼容 Forge 1.7.10-pre4,#4057
  708. Dim Versions As New List(Of DlForgeVersionEntry)
  709. Try
  710. Dim Recommended As String = McDownloadForgeRecommendedGet(Loader.Input)
  711. For Each Token As JObject In Json
  712. '分类与 Hash 获取
  713. Dim Hash As String = Nothing, Category As String = "unknown", Proi As Integer = -1
  714. For Each File As JObject In Token("files")
  715. Select Case File("category").ToString
  716. Case "installer"
  717. If File("format").ToString = "jar" Then
  718. '类型为 installer.jar,支持范围 ~753 (~ 1.6.1 部分), 738~684 (1.5.2 全部)
  719. Hash = File("hash")
  720. Category = "installer"
  721. Proi = 2
  722. End If
  723. Case "universal"
  724. If Proi <= 1 AndAlso File("format").ToString = "zip" Then
  725. '类型为 universal.zip,支持范围 751~449 (1.6.1 部分), 682~183 (1.5.1 ~ 1.3.2 部分)
  726. Hash = File("hash")
  727. Category = "universal"
  728. Proi = 1
  729. End If
  730. Case "client"
  731. If Proi <= 0 AndAlso File("format").ToString = "zip" Then
  732. '类型为 client.zip,支持范围 182~ (1.3.2 部分 ~)
  733. Hash = File("hash")
  734. Category = "client"
  735. Proi = 0
  736. End If
  737. End Select
  738. Next
  739. '获取 Entry
  740. Dim Branch As String = Token("branch")
  741. Dim Name As String = Token("version")
  742. '基础信息获取
  743. Dim Entry = New DlForgeVersionEntry(Name, Branch, Loader.Input) With {.Hash = Hash, .Category = Category, .IsRecommended = Recommended = Name}
  744. Dim TimeSplit = Token("modified").ToString.Split("-"c, "T"c, ":"c, "."c, " "c, "/"c)
  745. Entry.ReleaseTime = Token("modified").ToObject(Of Date).ToLocalTime.ToString("yyyy'/'MM'/'dd HH':'mm")
  746. '添加项
  747. Versions.Add(Entry)
  748. Next
  749. Catch ex As Exception
  750. Throw New Exception("Forge BMCLAPI 版本列表解析失败(" & Json.ToString & ")", ex)
  751. End Try
  752. If Not Versions.Any() Then Throw New Exception("无")
  753. Loader.Output = Versions
  754. End Sub
  755. #End Region
  756. #Region "DlNeoForgeList | NeoForge 版本列表"
  757. Public Structure DlNeoForgeListResult
  758. ''' <summary>
  759. ''' 数据来源名称,如“Official”,“BMCLAPI”。
  760. ''' </summary>
  761. Public SourceName As String
  762. ''' <summary>
  763. ''' 是否为官方的实时数据。
  764. ''' </summary>
  765. Public IsOfficial As Boolean
  766. ''' <summary>
  767. ''' 所有版本的列表。已经按从新到老排序。
  768. ''' </summary>
  769. Public Value As List(Of DlNeoForgeListEntry)
  770. End Structure
  771. Public Class DlNeoForgeListEntry
  772. Inherits DlForgelikeEntry
  773. ''' <summary>
  774. ''' 是否是 Beta 版。
  775. ''' </summary>
  776. Public IsBeta As Boolean
  777. ''' <summary>
  778. ''' API 使用的原始版本字符串,如 “20.4.30-beta”、“1.20.1-47.1.99”(Legacy)。
  779. ''' </summary>
  780. Public ApiName As String
  781. ''' <summary>
  782. ''' 文件在官网的基础地址,不包含后缀。
  783. ''' </summary>
  784. Public ReadOnly Property UrlBase As String
  785. Get
  786. Dim PackageName As String = If(Inherit = "1.20.1", "forge", "neoforge")
  787. Return $"https://maven.neoforged.net/releases/net/neoforged/{PackageName}/{ApiName}/{PackageName}-{ApiName}"
  788. End Get
  789. End Property
  790. ''' <summary>
  791. ''' 加载器名称。Forge 或 NeoForge。
  792. ''' </summary>
  793. Public Overrides ReadOnly Property LoaderName As String
  794. Get
  795. Return "NeoForge"
  796. End Get
  797. End Property
  798. ''' <summary>
  799. ''' 文件扩展名。不以小数点开头。
  800. ''' </summary>
  801. Public Overrides ReadOnly Property FileExtension As String
  802. Get
  803. Return "jar"
  804. End Get
  805. End Property
  806. Public Sub New(ApiName As String)
  807. IsNeoForge = True
  808. Me.ApiName = ApiName
  809. IsBeta = ApiName.ContainsIgnoreCase("beta") OrElse ApiName.ContainsIgnoreCase("alpha")
  810. If ApiName.Contains("1.20.1") Then '1.20.1-47.1.99
  811. VersionName = ApiName.Replace("1.20.1-", "")
  812. Version = New Version("19." & VersionName)
  813. Inherit = "1.20.1"
  814. ElseIf ApiName.StartsWithF("0.") Then '0.25w14craftmine.3-beta
  815. VersionName = ApiName
  816. Dim Segments = ApiName.BeforeFirst("-").Split("."c)
  817. Version = New Version(0, 0, Segments.Last)
  818. Inherit = Segments(1)
  819. Else '20.4.30-beta;26.1.0.0-alpha.1+snapshot-1
  820. VersionName = ApiName
  821. Version = New Version(ApiName.BeforeFirst("-"))
  822. If Version.Major >= 24 Then
  823. Inherit = $"{Version.Major}.{Version.Minor}.{Version.Build}"
  824. Else
  825. Inherit = $"1.{Version.Major}.{Version.Minor}"
  826. End If
  827. If Inherit.EndsWithF(".0") Then Inherit = Left(Inherit, Inherit.Length - 2)
  828. If VersionName.Contains("+") Then Inherit &= "-" & VersionName.AfterFirst("+")
  829. End If
  830. End Sub
  831. End Class
  832. ''' <summary>
  833. ''' NeoForge 版本列表,主加载器。
  834. ''' </summary>
  835. Public DlNeoForgeListLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Main", AddressOf DlNeoForgeListMain)
  836. Private Sub DlNeoForgeListMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
  837. Select Case Settings.Get(Of Integer)("ToolDownloadVersion")
  838. Case 0
  839. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)) From {
  840. New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListBmclapiLoader, 30),
  841. New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListOfficialLoader, 30 + 60)
  842. }, Loader.IsForceRestarting)
  843. Case 1
  844. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)) From {
  845. New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListOfficialLoader, 5),
  846. New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListBmclapiLoader, 5 + 30)
  847. }, Loader.IsForceRestarting)
  848. Case Else
  849. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)) From {
  850. New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListOfficialLoader, 60),
  851. New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListBmclapiLoader, 60 + 60)
  852. }, Loader.IsForceRestarting)
  853. End Select
  854. End Sub
  855. ''' <summary>
  856. ''' NeoForge 版本列表,官方源。
  857. ''' </summary>
  858. Public DlNeoForgeListOfficialLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Official", AddressOf DlNeoForgeListOfficialMain)
  859. Private Sub DlNeoForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
  860. '获取版本列表 JSON
  861. Dim ResultLatest As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge", RequireJson:=True)
  862. Dim ResultLegacy As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge", RequireJson:=True)
  863. If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
  864. '解析
  865. Try
  866. Loader.Output = New DlNeoForgeListResult With {.IsOfficial = True, .SourceName = "NeoForge 官方源",
  867. .Value = GetNeoForgeEntries(ResultLatest, ResultLegacy)}
  868. Catch ex As Exception
  869. Throw New Exception("NeoForge 官方源版本列表解析失败(" & ResultLatest & vbCrLf & vbCrLf & ResultLegacy & ")", ex)
  870. End Try
  871. End Sub
  872. ''' <summary>
  873. ''' NeoForge 版本列表,BMCLAPI。
  874. ''' </summary>
  875. Public DlNeoForgeListBmclapiLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Bmclapi", AddressOf DlNeoForgeListBmclapiMain)
  876. Public Sub DlNeoForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
  877. '获取版本列表 JSON
  878. Dim ResultLatest As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge", RequireJson:=True)
  879. Dim ResultLegacy As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge", RequireJson:=True)
  880. If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
  881. '解析
  882. Try
  883. Loader.Output = New DlNeoForgeListResult With {.IsOfficial = True, .SourceName = "BMCLAPI",
  884. .Value = GetNeoForgeEntries(ResultLatest, ResultLegacy)}
  885. Catch ex As Exception
  886. Throw New Exception("NeoForge BMCLAPI 版本列表解析失败(" & ResultLatest & vbCrLf & vbCrLf & ResultLegacy & ")", ex)
  887. End Try
  888. End Sub
  889. Private Function GetNeoForgeEntries(LatestJson As String, LatestLegacyJson As String) As List(Of DlNeoForgeListEntry)
  890. Dim VersionNames = (LatestLegacyJson & LatestJson).RegexSearch(
  891. "(?<="")(1\.20\.1-)?\d+\.[^\.]+\.\d+(\.\d+)?(-(beta|alpha)(\.\d+)?)?(\+snapshot-\d+)?(?="")")
  892. Dim Versions = VersionNames.
  893. Where(Function(name) name <> "47.1.82"). '这个版本虽然在版本列表中,但不能下载
  894. Select(Function(name) New DlNeoForgeListEntry(name)).
  895. OrderByDescending(Function(a) a).ToList
  896. If Not Versions.Any() Then Throw New Exception("无")
  897. Return Versions
  898. End Function
  899. #End Region
  900. #Region "DlLiteLoaderList | LiteLoader 版本列表"
  901. Public Structure DlLiteLoaderListResult
  902. ''' <summary>
  903. ''' 数据来源名称,如“Official”,“BMCLAPI”。
  904. ''' </summary>
  905. Public SourceName As String
  906. ''' <summary>
  907. ''' 是否为官方的实时数据。
  908. ''' </summary>
  909. Public IsOfficial As Boolean
  910. ''' <summary>
  911. ''' 获取到的数据。
  912. ''' </summary>
  913. Public Value As List(Of DlLiteLoaderListEntry)
  914. ''' <summary>
  915. ''' 官方源的失败原因。若没有则为 Nothing。
  916. ''' </summary>
  917. Public OfficialError As Exception
  918. End Structure
  919. Public Class DlLiteLoaderListEntry
  920. ''' <summary>
  921. ''' 实际的文件名,如“liteloader-installer-1.12-00-SNAPSHOT.jar”。
  922. ''' </summary>
  923. Public FileName As String
  924. ''' <summary>
  925. ''' 是否为测试版。
  926. ''' </summary>
  927. Public IsPreview As Boolean
  928. ''' <summary>
  929. ''' 对应的 Minecraft 版本,如“1.12.2”。
  930. ''' </summary>
  931. Public Inherit As String
  932. ''' <summary>
  933. ''' 是否为 1.7 及更早的远古版。
  934. ''' </summary>
  935. Public IsLegacy As Boolean
  936. ''' <summary>
  937. ''' 发布时间,格式为“yyyy/mm/dd HH:mm”。
  938. ''' </summary>
  939. Public ReleaseTime As String
  940. ''' <summary>
  941. ''' 文件的 MD5。
  942. ''' </summary>
  943. Public MD5 As String
  944. ''' <summary>
  945. ''' 对应的 Json 项。
  946. ''' </summary>
  947. Public JsonToken As JToken
  948. End Class
  949. ''' <summary>
  950. ''' LiteLoader 版本列表,主加载器。
  951. ''' </summary>
  952. Public DlLiteLoaderListLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Main", AddressOf DlLiteLoaderListMain)
  953. Private Sub DlLiteLoaderListMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
  954. Select Case Settings.Get(Of Integer)("ToolDownloadVersion")
  955. Case 0
  956. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)) From {
  957. New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListBmclapiLoader, 30),
  958. New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListOfficialLoader, 30 + 60)
  959. }, Loader.IsForceRestarting)
  960. Case 1
  961. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)) From {
  962. New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListOfficialLoader, 5),
  963. New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListBmclapiLoader, 5 + 30)
  964. }, Loader.IsForceRestarting)
  965. Case Else
  966. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)) From {
  967. New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListOfficialLoader, 60),
  968. New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListBmclapiLoader, 60 + 60)
  969. }, Loader.IsForceRestarting)
  970. End Select
  971. End Sub
  972. ''' <summary>
  973. ''' LiteLoader 版本列表,官方源。
  974. ''' </summary>
  975. Public DlLiteLoaderListOfficialLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Official", AddressOf DlLiteLoaderListOfficialMain)
  976. Private Sub DlLiteLoaderListOfficialMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
  977. Dim Result As JObject = NetRequestByClientRetry("https://dl.liteloader.com/versions/versions.json", RequireJson:=True).DeserializeJson()
  978. Try
  979. Dim Json As JObject = Result("versions")
  980. Dim Versions As New List(Of DlLiteLoaderListEntry)
  981. For Each Pair As KeyValuePair(Of String, JToken) In Json
  982. If Pair.Key.StartsWithF("1.6") OrElse Pair.Key.StartsWithF("1.5") Then Continue For
  983. Dim RealEntry As JToken = If(Pair.Value("artefacts"), Pair.Value("snapshots"))("com.mumfrey:liteloader")("latest")
  984. Versions.Add(New DlLiteLoaderListEntry With {
  985. .Inherit = Pair.Key,
  986. .IsLegacy = Pair.Key.Split(".")(1) < 8,
  987. .IsPreview = RealEntry("stream").ToString.Lower = "snapshot",
  988. .FileName = "liteloader-installer-" & Pair.Key & If(Pair.Key = "1.8" OrElse Pair.Key = "1.9", ".0", "") & "-00-SNAPSHOT.jar",
  989. .MD5 = RealEntry("md5"),
  990. .ReleaseTime = GetLocalTime(GetDate(RealEntry("timestamp"))).ToString("yyyy'/'MM'/'dd HH':'mm"),
  991. .JsonToken = RealEntry
  992. })
  993. Next
  994. Loader.Output = New DlLiteLoaderListResult With {.IsOfficial = True, .SourceName = "LiteLoader 官方源", .Value = Versions}
  995. Catch ex As Exception
  996. Throw New Exception("LiteLoader 官方源版本列表解析失败(" & Result.ToString & ")", ex)
  997. End Try
  998. End Sub
  999. ''' <summary>
  1000. ''' LiteLoader 版本列表,BMCLAPI。
  1001. ''' </summary>
  1002. Public DlLiteLoaderListBmclapiLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Bmclapi", AddressOf DlLiteLoaderListBmclapiMain)
  1003. Private Sub DlLiteLoaderListBmclapiMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
  1004. Dim Result As JObject = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json", RequireJson:=True).DeserializeJson()
  1005. Try
  1006. Dim Json As JObject = Result("versions")
  1007. Dim Versions As New List(Of DlLiteLoaderListEntry)
  1008. For Each Pair As KeyValuePair(Of String, JToken) In Json
  1009. If Pair.Key.StartsWithF("1.6") OrElse Pair.Key.StartsWithF("1.5") Then Continue For
  1010. Dim RealEntry As JToken = If(Pair.Value("artefacts"), Pair.Value("snapshots"))("com.mumfrey:liteloader")("latest")
  1011. Versions.Add(New DlLiteLoaderListEntry With {
  1012. .Inherit = Pair.Key,
  1013. .IsLegacy = Pair.Key.Split(".")(1) < 8,
  1014. .IsPreview = RealEntry("stream").ToString.Lower = "snapshot",
  1015. .FileName = "liteloader-installer-" & Pair.Key & If(Pair.Key = "1.8" OrElse Pair.Key = "1.9", ".0", "") & "-00-SNAPSHOT.jar",
  1016. .MD5 = RealEntry("md5"),
  1017. .ReleaseTime = GetLocalTime(GetDate(RealEntry("timestamp"))).ToString("yyyy'/'MM'/'dd HH':'mm"),
  1018. .JsonToken = RealEntry
  1019. })
  1020. Next
  1021. Loader.Output = New DlLiteLoaderListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Versions}
  1022. Catch ex As Exception
  1023. Throw New Exception("LiteLoader BMCLAPI 版本列表解析失败(" & Result.ToString & ")", ex)
  1024. End Try
  1025. End Sub
  1026. #End Region
  1027. #Region "DlFabricList | Fabric 列表"
  1028. Public Structure DlFabricListResult
  1029. ''' <summary>
  1030. ''' 数据来源名称,如“Official”,“BMCLAPI”。
  1031. ''' </summary>
  1032. Public SourceName As String
  1033. ''' <summary>
  1034. ''' 是否为官方的实时数据。
  1035. ''' </summary>
  1036. Public IsOfficial As Boolean
  1037. ''' <summary>
  1038. ''' 获取到的数据。
  1039. ''' </summary>
  1040. Public Value As JObject
  1041. End Structure
  1042. ''' <summary>
  1043. ''' Fabric 列表,主加载器。
  1044. ''' </summary>
  1045. Public DlFabricListLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Main", AddressOf DlFabricListMain)
  1046. Private Sub DlFabricListMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
  1047. Select Case Settings.Get(Of Integer)("ToolDownloadVersion")
  1048. Case 0
  1049. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)) From {
  1050. New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListBmclapiLoader, 30),
  1051. New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListOfficialLoader, 30 + 60)
  1052. }, Loader.IsForceRestarting)
  1053. Case 1
  1054. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)) From {
  1055. New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListOfficialLoader, 5),
  1056. New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListBmclapiLoader, 5 + 30)
  1057. }, Loader.IsForceRestarting)
  1058. Case Else
  1059. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)) From {
  1060. New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListOfficialLoader, 60),
  1061. New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListBmclapiLoader, 60 + 60)
  1062. }, Loader.IsForceRestarting)
  1063. End Select
  1064. End Sub
  1065. ''' <summary>
  1066. ''' Fabric 列表,官方源。
  1067. ''' </summary>
  1068. Public DlFabricListOfficialLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Official", AddressOf DlFabricListOfficialMain)
  1069. Private Sub DlFabricListOfficialMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
  1070. Dim Result As JObject = NetRequestByClientRetry("https://meta.fabricmc.net/v2/versions", RequireJson:=True).DeserializeJson()
  1071. Try
  1072. Dim Output = New DlFabricListResult With {.IsOfficial = True, .SourceName = "Fabric 官方源", .Value = Result}
  1073. If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
  1074. Loader.Output = Output
  1075. Catch ex As Exception
  1076. Throw New Exception("Fabric 官方源版本列表解析失败(" & Result.ToString & ")", ex)
  1077. End Try
  1078. End Sub
  1079. ''' <summary>
  1080. ''' Fabric 列表,BMCLAPI。
  1081. ''' </summary>
  1082. Public DlFabricListBmclapiLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Bmclapi", AddressOf DlFabricListBmclapiMain)
  1083. Private Sub DlFabricListBmclapiMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
  1084. Dim Result As JObject = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions", RequireJson:=True).DeserializeJson()
  1085. Try
  1086. Dim Output = New DlFabricListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Result}
  1087. If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
  1088. Loader.Output = Output
  1089. Catch ex As Exception
  1090. Throw New Exception("Fabric BMCLAPI 版本列表解析失败(" & Result.ToString & ")", ex)
  1091. End Try
  1092. End Sub
  1093. ''' <summary>
  1094. ''' Fabric API 列表,官方源。
  1095. ''' </summary>
  1096. Public DlFabricApiLoader As New LoaderTask(Of Integer, List(Of ResourceVersion))("Fabric API List Loader",
  1097. Sub(Task As LoaderTask(Of Integer, List(Of ResourceVersion))) Task.Output = ResourceVersion.FromProjectId("fabric-api", ResourcePlatforms.Modrinth, LoadDependencies:=False))
  1098. ''' <summary>
  1099. ''' OptiFabric 列表,官方源。
  1100. ''' </summary>
  1101. Public DlOptiFabricLoader As New LoaderTask(Of Integer, List(Of ResourceVersion))("OptiFabric List Loader",
  1102. Sub(Task As LoaderTask(Of Integer, List(Of ResourceVersion))) Task.Output = ResourceVersion.FromProjectId("322385", ResourcePlatforms.CurseForge, LoadDependencies:=False))
  1103. #End Region
  1104. #Region "DlMod | Mod 镜像源请求"
  1105. ''' <summary>
  1106. ''' 对可能涉及 Mod 镜像源的请求进行处理,返回 JToken。
  1107. ''' 调用 NetRequest,会进行重试。
  1108. ''' </summary>
  1109. Public Function DlModRequest(Url As String, Optional Method As HttpMethod = Nothing,
  1110. Optional Content As String = Nothing, Optional ContentType As String = Nothing) As JToken
  1111. Dim Urls As New List(Of (Url As String, Timeout As Integer))
  1112. Dim McimUrl As String = DlSourceModGet(Url)
  1113. If McimUrl <> Url Then
  1114. Select Case Settings.Get(Of Integer)("ToolDownloadMod")
  1115. Case 0
  1116. Urls.Add((McimUrl, 10))
  1117. Urls.Add((McimUrl, 10))
  1118. Urls.Add((Url, 30))
  1119. Case 1
  1120. Urls.Add((Url, If(Url.Contains("modrinth"), 20, 10))) '至少 20s,要不然 Modrinth 返回不过来
  1121. Urls.Add((McimUrl, 10))
  1122. Urls.Add((Url, 30))
  1123. Urls.Add((McimUrl, 30))
  1124. Case Else
  1125. Urls.Add((Url, If(Url.Contains("modrinth"), 20, 10)))
  1126. Urls.Add((Url, 30))
  1127. Urls.Add((McimUrl, 30))
  1128. End Select
  1129. Else
  1130. Urls.Add((Url, If(Url.Contains("modrinth"), 20, 10)))
  1131. Urls.Add((Url, 30))
  1132. End If
  1133. Dim Errors As New List(Of Exception)
  1134. For Each Source In Urls
  1135. Try
  1136. Return NetRequestByClient(Source.Url, Method, Content, ContentType, Timeout:=Source.Timeout * 1000, Encoding:=Encoding.UTF8, RequireJson:=True).DeserializeJson()
  1137. Catch ex As Exception
  1138. Errors.Add(ex)
  1139. End Try
  1140. Next
  1141. Throw New AggregateException(Errors)
  1142. End Function
  1143. #End Region
  1144. #Region "DlSource | 镜像下载源"
  1145. Private DlPreferMojang As Boolean = False
  1146. ''' <summary>
  1147. ''' 下载文件(而非获取版本列表)的时候,是否优先使用官方源。
  1148. ''' </summary>
  1149. Public ReadOnly Property DlSourcePreferMojang As Boolean
  1150. Get
  1151. Return Settings.Get(Of Integer)("ToolDownloadSource") = 2 OrElse (Settings.Get(Of Integer)("ToolDownloadSource") = 1 AndAlso DlPreferMojang)
  1152. End Get
  1153. End Property
  1154. ''' <summary>
  1155. ''' 下载文件(而非获取版本列表)的时候,根据是否优先使用官方源决定使用 Url 的顺序。
  1156. ''' </summary>
  1157. Public Function DlSourceOrder(OfficialUrls As IEnumerable(Of String), MirrorUrls As IEnumerable(Of String)) As IEnumerable(Of String)
  1158. Return If(DlSourcePreferMojang, OfficialUrls.Union(MirrorUrls), MirrorUrls.Union(OfficialUrls))
  1159. End Function
  1160. ''' <summary>
  1161. ''' 获取版本列表(而非下载文件)的时候,是否优先使用官方源。
  1162. ''' </summary>
  1163. Public ReadOnly Property DlVersionListPreferMojang As Boolean
  1164. Get
  1165. Return Settings.Get(Of Integer)("ToolDownloadVersion") = 2 OrElse (Settings.Get(Of Integer)("ToolDownloadVersion") = 1 AndAlso DlPreferMojang)
  1166. End Get
  1167. End Property
  1168. ''' <summary>
  1169. ''' 获取版本列表(而非下载文件)的时候,根据是否优先使用官方源决定使用 Url 的顺序。
  1170. ''' </summary>
  1171. Public Function DlVersionListOrder(OfficialUrls As IEnumerable(Of String), MirrorUrls As IEnumerable(Of String)) As IEnumerable(Of String)
  1172. Return If(DlVersionListPreferMojang, OfficialUrls.Union(MirrorUrls), MirrorUrls.Union(OfficialUrls))
  1173. End Function
  1174. ''' <summary>
  1175. ''' 下载 Assets 文件。
  1176. ''' </summary>
  1177. Public Function DlSourceAssetsGet(Original As String) As IEnumerable(Of String)
  1178. Original = Original.Replace("http://resources.download.minecraft.net", "https://resources.download.minecraft.net")
  1179. Return DlSourceOrder(
  1180. {Original},
  1181. {Original.
  1182. Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/assets").
  1183. Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/assets").
  1184. Replace("https://resources.download.minecraft.net", "https://bmclapi2.bangbang93.com/assets")
  1185. })
  1186. End Function
  1187. ''' <summary>
  1188. ''' 下载 Libraries 文件。
  1189. ''' </summary>
  1190. Public Function DlSourceLibraryGet(Original As String) As IEnumerable(Of String)
  1191. If {"minecraftforge", "fabricmc", "neoforged"}.Any(Function(k) Original.Contains(k)) Then '不添加原版源
  1192. Return {
  1193. Original.
  1194. Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/maven").
  1195. Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/maven").
  1196. Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/maven"),
  1197. Original.
  1198. Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/libraries").
  1199. Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/libraries").
  1200. Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
  1201. }
  1202. Else
  1203. Return DlSourceOrder(
  1204. {Original},
  1205. {Original.
  1206. Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/maven").
  1207. Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/maven").
  1208. Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/maven"),
  1209. Original.
  1210. Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/libraries").
  1211. Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/libraries").
  1212. Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
  1213. })
  1214. End If
  1215. End Function
  1216. ''' <summary>
  1217. ''' 下载 Launcher 或 Meta 文件。
  1218. ''' 不应使用它来获取版本列表(因为它只使用文件下载源设置来决定源顺序)。
  1219. ''' </summary>
  1220. Public Function DlSourceLauncherOrMetaGet(Original As String) As IEnumerable(Of String)
  1221. If Original Is Nothing Then Throw New Exception("无对应的 json 下载地址")
  1222. Return DlSourceOrder(
  1223. {Original},
  1224. {Original.
  1225. Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com").
  1226. Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com").
  1227. Replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com").
  1228. Replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com")
  1229. })
  1230. End Function
  1231. 'Mod 下载源
  1232. Public Function DlSourceModGet(Original As String) As String
  1233. Return Original.
  1234. Replace("api.modrinth.com", "mod.mcimirror.top/modrinth").
  1235. Replace("staging-api.modrinth.com", "mod.mcimirror.top/modrinth").
  1236. Replace("cdn.modrinth.com", "mod.mcimirror.top").
  1237. Replace("api.curseforge.com", "mod.mcimirror.top/curseforge").
  1238. Replace("edge.forgecdn.net", "mod.mcimirror.top").
  1239. Replace("mediafilez.forgecdn.net", "mod.mcimirror.top").
  1240. Replace("media.forgecdn.net", "mod.mcimirror.top")
  1241. End Function
  1242. 'Loader 自动切换
  1243. Private Sub DlSourceLoader(Of InputType, OutputType)(MainLoader As LoaderTask(Of InputType, OutputType),
  1244. LoaderList As List(Of KeyValuePair(Of LoaderTask(Of InputType, OutputType), Integer)),
  1245. Optional IsForceRestart As Boolean = False)
  1246. Dim WaitCycle As Integer = 0
  1247. Do While True
  1248. '检查状态
  1249. Dim BeforeLoadersAllFailed As Boolean = True
  1250. For Each SubLoader In LoaderList
  1251. If WaitCycle = 0 Then '判断是否可以不加载,直接使用已经加载好的结果
  1252. If IsForceRestart Then Continue For '强制刷新,不行
  1253. If (SubLoader.Key.Input Is Nothing Xor MainLoader.Input Is Nothing) OrElse
  1254. (SubLoader.Key.Input IsNot Nothing AndAlso Not SubLoader.Key.Input.Equals(MainLoader.Input)) Then Continue For '父子加载器的输入不一样,也不行
  1255. End If
  1256. If SubLoader.Key.State <> LoadState.Failed Then BeforeLoadersAllFailed = False
  1257. If SubLoader.Key.State = LoadState.Finished Then
  1258. '检查加载器成功
  1259. MainLoader.Output = SubLoader.Key.Output
  1260. DlSourceLoaderCancel(LoaderList)
  1261. Return
  1262. ElseIf BeforeLoadersAllFailed Then
  1263. '此前的加载器全部失败,直接启动后续加载器
  1264. If WaitCycle < SubLoader.Value * 100 Then WaitCycle = SubLoader.Value * 100
  1265. End If
  1266. Next
  1267. '第一轮时:既然不直接使用已经加载好的结果,那就启动第一个加载器
  1268. If WaitCycle = 0 Then
  1269. LoaderList.First.Key.Start(MainLoader.Input, IsForceRestart)
  1270. For Each Loader In LoaderList.Skip(1)
  1271. Loader.Key.State = LoadState.Waiting '将其他源标记为未启动,以确保可以切换下载源(#184)
  1272. Next
  1273. End If
  1274. '检查加载器失败或超时
  1275. For i = 0 To LoaderList.Count - 1
  1276. If WaitCycle <> LoaderList(i).Value * 100 Then Continue For
  1277. If i < LoaderList.Count - 1 AndAlso Not LoaderList.All(Function(l) l.Key.State = LoadState.Failed) Then
  1278. '若还有下一个源,则启动下一个源
  1279. LoaderList(i + 1).Key.Start(MainLoader.Input, IsForceRestart)
  1280. Else
  1281. '若没有,则失败
  1282. Dim ErrorInfo As Exception = Nothing
  1283. For ii = 0 To LoaderList.Count - 1
  1284. LoaderList(ii).Key.Input = Nothing '重置输入,以免以同样的输入“重试加载”时直接失败
  1285. If LoaderList(ii).Key.Error IsNot Nothing Then
  1286. If ErrorInfo Is Nothing OrElse LoaderList(ii).Key.Error.Message = "无" Then
  1287. ErrorInfo = LoaderList(ii).Key.Error
  1288. End If
  1289. End If
  1290. Next
  1291. If ErrorInfo Is Nothing Then ErrorInfo = New TimeoutException("下载源连接超时")
  1292. DlSourceLoaderCancel(LoaderList)
  1293. Throw ErrorInfo
  1294. End If
  1295. Exit For
  1296. Next
  1297. '计时
  1298. Thread.Sleep(10)
  1299. WaitCycle += 1
  1300. '检查父加载器中断
  1301. If MainLoader.IsCanceled Then
  1302. DlSourceLoaderCancel(LoaderList)
  1303. Return
  1304. End If
  1305. Loop
  1306. End Sub
  1307. Private Sub DlSourceLoaderCancel(Of InputType, OutputType)(LoaderList As List(Of KeyValuePair(Of LoaderTask(Of InputType, OutputType), Integer)))
  1308. For Each Loader In LoaderList
  1309. If Loader.Key.State = LoadState.Loading Then Loader.Key.Cancel()
  1310. Next
  1311. End Sub
  1312. #End Region
  1313. End Module