ModDownload.vb 95 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777
  1. Public Module ModDownload
  2. #Region "DlClient* | Minecraft 客户端"
  3. ''' <summary>
  4. ''' 返回某 Minecraft 版本对应的原版主 Jar 文件的下载信息,要求对应依赖版本已存在。
  5. ''' 失败则抛出异常,不需要下载则返回 Nothing。
  6. ''' </summary>
  7. Public Function DlClientJarGet(Version As McVersion, ReturnNothingOnFileUseable As Boolean) As NetFile
  8. '获取底层继承版本
  9. Do While Not String.IsNullOrEmpty(Version.InheritVersion)
  10. Version = New McVersion(Version.InheritVersion)
  11. Loop
  12. '检查 Json 是否标准
  13. If Version.JsonObject("downloads") Is Nothing OrElse Version.JsonObject("downloads")("client") Is Nothing OrElse Version.JsonObject("downloads")("client")("url") Is Nothing Then
  14. Throw New Exception("底层版本 " & Version.Name & " 中无 Jar 文件下载信息")
  15. End If
  16. '检查文件
  17. Dim Checker As New FileChecker(MinSize:=1024, ActualSize:=If(Version.JsonObject("downloads")("client")("size"), -1), Hash:=Version.JsonObject("downloads")("client")("sha1"))
  18. If ReturnNothingOnFileUseable Then
  19. '是否跳过
  20. Dim IsSetupSkip As Boolean = Setup.Get("LaunchAdvanceAssets")
  21. Select Case Setup.Get("VersionAdvanceAssets", Version:=Version)
  22. Case 0 '使用全局设置
  23. Case 1 '开启
  24. IsSetupSkip = False
  25. Case 2 '关闭
  26. IsSetupSkip = True
  27. End Select
  28. If IsSetupSkip AndAlso File.Exists(Version.Path & Version.Name & ".jar") Then Return Nothing '跳过校验
  29. If Checker.Check(Version.Path & Version.Name & ".jar") Is Nothing Then Return Nothing '通过校验
  30. End If
  31. '返回下载信息
  32. Dim JarUrl As String = Version.JsonObject("downloads")("client")("url")
  33. Return New NetFile(DlSourceLauncherOrMetaGet(JarUrl), Version.Path & Version.Name & ".jar", Checker)
  34. End Function
  35. ''' <summary>
  36. ''' 返回某 Minecraft 版本对应的原版主 AssetIndex 文件的下载信息,要求对应依赖版本已存在。
  37. ''' 若未找到,则会返回 Legacy 资源文件或 Nothing。
  38. ''' </summary>
  39. Public Function DlClientAssetIndexGet(Version As McVersion) As NetFile
  40. '获取底层继承版本
  41. Do While Not String.IsNullOrEmpty(Version.InheritVersion)
  42. Version = New McVersion(Version.InheritVersion)
  43. Loop
  44. '获取信息
  45. Dim IndexInfo = McAssetsGetIndex(Version, True, True)
  46. Dim IndexAddress As String = PathMcFolder & "assets\indexes\" & IndexInfo("id").ToString & ".json"
  47. Log("[Download] 版本 " & Version.Name & " 对应的资源文件索引为 " & IndexInfo("id").ToString)
  48. Dim IndexUrl As String = If(IndexInfo("url"), "")
  49. If IndexUrl = "" Then
  50. Return Nothing
  51. Else
  52. Return New NetFile(DlSourceLauncherOrMetaGet(IndexUrl, False), IndexAddress, New FileChecker(CanUseExistsFile:=False, IsJson:=True))
  53. End If
  54. End Function
  55. ''' <summary>
  56. ''' 构造补全某 Minecraft 版本的所有文件的加载器列表。失败会抛出异常。
  57. ''' </summary>
  58. Public Function DlClientFix(Version As McVersion, CheckAssetsHash As Boolean, AssetsIndexBehaviour As AssetsIndexExistsBehaviour, SkipAssetsDownloadWhileSetupRequired As Boolean) As List(Of LoaderBase)
  59. Dim Loaders As New List(Of LoaderBase)
  60. #Region "下载支持库文件"
  61. Dim LoadersLib As New List(Of LoaderBase) From {
  62. New LoaderTask(Of String, List(Of NetFile))("分析缺失支持库文件", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(Version)) With {.ProgressWeight = 1},
  63. New LoaderDownload("下载支持库文件", New List(Of NetFile)) With {.ProgressWeight = 15}
  64. }
  65. '构造加载器
  66. Loaders.Add(New LoaderCombo(Of String)("下载支持库文件(主加载器)", LoadersLib) With {.Block = False, .Show = False, .ProgressWeight = 16})
  67. #End Region
  68. #Region "下载资源文件"
  69. Dim IsSetupSkip As Boolean = Setup.Get("LaunchAdvanceAssets")
  70. Select Case Setup.Get("VersionAdvanceAssets", Version:=Version)
  71. Case 0
  72. '使用全局设置
  73. Case 1
  74. '开启
  75. IsSetupSkip = False
  76. Case 2
  77. '关闭
  78. IsSetupSkip = True
  79. End Select
  80. If IsSetupSkip Then Log("[Download] 已跳过 Assets 下载")
  81. If (Not SkipAssetsDownloadWhileSetupRequired) OrElse Not IsSetupSkip Then
  82. Dim LoadersAssets As New List(Of LoaderBase)
  83. '获取资源文件索引地址
  84. LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析资源文件索引地址",
  85. Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  86. Try
  87. Dim IndexFile = DlClientAssetIndexGet(Version)
  88. Dim IndexFileInfo As New FileInfo(IndexFile.LocalPath)
  89. If AssetsIndexBehaviour <> AssetsIndexExistsBehaviour.AlwaysDownload AndAlso IndexFile.Check.Check(IndexFile.LocalPath) Is Nothing Then
  90. Task.Output = New List(Of NetFile)
  91. Else
  92. Task.Output = New List(Of NetFile) From {IndexFile}
  93. End If
  94. Catch ex As Exception
  95. Throw New Exception("分析资源文件索引地址失败", ex)
  96. End Try
  97. End Sub) With {.ProgressWeight = 0.5, .Show = False})
  98. '下载资源文件索引
  99. LoadersAssets.Add(New LoaderDownload("下载资源文件索引", New List(Of NetFile)) With {.ProgressWeight = 2})
  100. '要求独立更新索引
  101. If AssetsIndexBehaviour = AssetsIndexExistsBehaviour.DownloadInBackground Then
  102. Dim LoadersAssetsUpdate As New List(Of LoaderBase)
  103. Dim TempAddress As String = Nothing
  104. Dim RealAddress As String = Nothing
  105. LoadersAssetsUpdate.Add(New LoaderTask(Of String, List(Of NetFile))("后台分析资源文件索引地址", Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  106. Dim BackAssetsFile As NetFile = DlClientAssetIndexGet(Version)
  107. RealAddress = BackAssetsFile.LocalPath
  108. TempAddress = PathTemp & "Cache\" & BackAssetsFile.LocalName
  109. BackAssetsFile.LocalPath = TempAddress
  110. Task.Output = New List(Of NetFile) From {BackAssetsFile}
  111. End Sub))
  112. LoadersAssetsUpdate.Add(New LoaderDownload("后台下载资源文件索引", New List(Of NetFile)))
  113. LoadersAssetsUpdate.Add(New LoaderTask(Of List(Of NetFile), String)("后台复制资源文件索引", Sub(Task As LoaderTask(Of List(Of NetFile), String))
  114. CopyFile(TempAddress, RealAddress)
  115. McLaunchLog("后台更新资源文件索引成功:" & TempAddress)
  116. End Sub))
  117. Dim Updater As New LoaderCombo(Of String)("后台更新资源文件索引", LoadersAssetsUpdate)
  118. Log("[Download] 开始后台更新资源文件索引")
  119. Updater.Start()
  120. End If
  121. '获取资源文件地址
  122. LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析缺失资源文件",
  123. Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  124. Task.Output = McAssetsFixList(McAssetsGetIndexName(Version), CheckAssetsHash, Task)
  125. End Sub) With {.ProgressWeight = 3})
  126. '下载资源文件
  127. LoadersAssets.Add(New LoaderDownload("下载资源文件", New List(Of NetFile)) With {.ProgressWeight = 25})
  128. '构造加载器
  129. Loaders.Add(New LoaderCombo(Of String)("下载资源文件(主加载器)", LoadersAssets) With {.Block = False, .Show = False, .ProgressWeight = 30.5})
  130. End If
  131. #End Region
  132. Return Loaders
  133. End Function
  134. Public Enum AssetsIndexExistsBehaviour
  135. ''' <summary>
  136. ''' 如果文件存在,则不进行下载。
  137. ''' </summary>
  138. DontDownload
  139. ''' <summary>
  140. ''' 如果文件存在,则启动新的下载加载器进行独立的更新。
  141. ''' </summary>
  142. DownloadInBackground
  143. ''' <summary>
  144. ''' 如果文件存在,也同样进行下载。
  145. ''' </summary>
  146. AlwaysDownload
  147. End Enum
  148. #End Region
  149. #Region "DlClientList | Minecraft 客户端 版本列表"
  150. '主加载器
  151. Public Structure DlClientListResult
  152. ''' <summary>
  153. ''' 数据来源名称,如“Mojang”,“BMCLAPI”。
  154. ''' </summary>
  155. Public SourceName As String
  156. ''' <summary>
  157. ''' 是否为官方的实时数据。
  158. ''' </summary>
  159. Public IsOfficial As Boolean
  160. ''' <summary>
  161. ''' 获取到的 Json 数据。
  162. ''' </summary>
  163. Public Value As JObject
  164. '''' <summary>
  165. '''' 官方源的失败原因。若没有则为 Nothing。
  166. '''' </summary>
  167. 'Public OfficialError As Exception
  168. End Structure
  169. ''' <summary>
  170. ''' Minecraft 客户端 版本列表,主加载器。
  171. ''' </summary>
  172. Public DlClientListLoader As New LoaderTask(Of Integer, DlClientListResult)("DlClientList Main", AddressOf DlClientListMain)
  173. Private Sub DlClientListMain(Loader As LoaderTask(Of Integer, DlClientListResult))
  174. Select Case Setup.Get("ToolDownloadVersion")
  175. Case 0
  176. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)) From {
  177. New KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)(DlClientListBmclapiLoader, 30),
  178. New KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)(DlClientListMojangLoader, 60)
  179. }, Loader.IsForceRestarting)
  180. Case 1
  181. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)) From {
  182. New KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)(DlClientListMojangLoader, 5),
  183. New KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)(DlClientListBmclapiLoader, 35)
  184. }, Loader.IsForceRestarting)
  185. Case Else
  186. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)) From {
  187. New KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)(DlClientListMojangLoader, 60),
  188. New KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)(DlClientListBmclapiLoader, 60)
  189. }, Loader.IsForceRestarting)
  190. End Select
  191. End Sub
  192. '各个下载源的分加载器
  193. ''' <summary>
  194. ''' Minecraft 客户端 版本列表,Mojang 官方源加载器。
  195. ''' </summary>
  196. Public DlClientListMojangLoader As New LoaderTask(Of Integer, DlClientListResult)("DlClientList Mojang", AddressOf DlClientListMojangMain)
  197. Private IsNewClientVersionHinted As Boolean = False
  198. Private Sub DlClientListMojangMain(Loader As LoaderTask(Of Integer, DlClientListResult))
  199. Dim Json As JObject = NetGetCodeByRequestRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json", IsJson:=True)
  200. Try
  201. Dim Versions As JArray = Json("versions")
  202. If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
  203. '添加 PCL2 特供项
  204. If File.Exists(PathTemp & "Cache\download.json") Then Versions.Merge(GetJson(ReadFile(PathTemp & "Cache\download.json")))
  205. '返回
  206. Loader.Output = New DlClientListResult With {.IsOfficial = True, .SourceName = "Mojang 官方源", .Value = Json}
  207. '解析更新提示(Release)
  208. Dim Version As String = Json("latest")("release")
  209. If Setup.Get("ToolUpdateRelease") AndAlso Not Setup.Get("ToolUpdateReleaseLast") = "" AndAlso Version IsNot Nothing AndAlso Not Setup.Get("ToolUpdateReleaseLast") = Version Then
  210. McDownloadClientUpdateHint(Version, Json)
  211. IsNewClientVersionHinted = True
  212. End If
  213. McVersionHighest = Version.Split(".")(1)
  214. Setup.Set("ToolUpdateReleaseLast", Version)
  215. '解析更新提示(Snapshot)
  216. Version = Json("latest")("snapshot")
  217. If Setup.Get("ToolUpdateSnapshot") AndAlso Not Setup.Get("ToolUpdateSnapshotLast") = "" AndAlso Version IsNot Nothing AndAlso Not Setup.Get("ToolUpdateSnapshotLast") = Version AndAlso Not IsNewClientVersionHinted Then
  218. McDownloadClientUpdateHint(Version, Json)
  219. End If
  220. Setup.Set("ToolUpdateSnapshotLast", If(Version, "Nothing"))
  221. Catch ex As Exception
  222. Throw New Exception("版本列表解析失败", ex)
  223. End Try
  224. End Sub
  225. ''' <summary>
  226. ''' Minecraft 客户端 版本列表,BMCLAPI 源加载器。
  227. ''' </summary>
  228. Public DlClientListBmclapiLoader As New LoaderTask(Of Integer, DlClientListResult)("DlClientList Bmclapi", AddressOf DlClientListBmclapiMain)
  229. Private Sub DlClientListBmclapiMain(Loader As LoaderTask(Of Integer, DlClientListResult))
  230. Dim Json As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json",
  231. BackupUrl:="https://download.mcbbs.net/mc/game/version_manifest.json",
  232. IsJson:=True)
  233. Try
  234. Dim Versions As JArray = Json("versions")
  235. If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
  236. '添加 PCL2 特供项
  237. If File.Exists(PathTemp & "Cache\download.json") Then Versions.Merge(GetJson(ReadFile(PathTemp & "Cache\download.json")))
  238. '返回
  239. Loader.Output = New DlClientListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Json}
  240. Catch ex As Exception
  241. Throw New Exception("版本列表解析失败(" & Json.ToString & ")", ex)
  242. End Try
  243. End Sub
  244. ''' <summary>
  245. ''' 获取某个版本的 Json 下载地址,若失败则返回 Nothing。必须在工作线程执行。
  246. ''' </summary>
  247. Public Function DlClientListGet(Id As String)
  248. Try
  249. '确认 Minecraft 版本列表已完成获取
  250. Select Case DlClientListLoader.State
  251. Case LoadState.Loading
  252. DlClientListLoader.WaitForExit()
  253. Case LoadState.Failed, LoadState.Aborted, LoadState.Waiting
  254. DlClientListLoader.WaitForExit(IsForceRestart:=True)
  255. End Select
  256. '确认版本格式标准
  257. Id = Id.Replace("_", "-") '1.7.10_pre4 在版本列表中显示为 1.7.10-pre4
  258. If Id <> "1.0" AndAlso Id.EndsWith(".0") Then Id = Left(Id, Id.Length - 2) 'OptiFine 1.8 的下载会触发此问题,显示版本为 1.8.0
  259. '查找版本并开始
  260. For Each Version As JObject In DlClientListLoader.Output.Value("versions")
  261. If Version("id") = Id Then
  262. Return Version("url").ToString
  263. End If
  264. Next
  265. Log("未发现版本 " & Id & " 的 Json 下载地址,版本列表返回为:" & vbCrLf & DlClientListLoader.Output.Value.ToString, LogLevel.Debug)
  266. Return Nothing
  267. Catch ex As Exception
  268. Log(ex, "获取版本 " & Id & " 的 Json 下载地址失败")
  269. Return Nothing
  270. End Try
  271. End Function
  272. #End Region
  273. #Region "DlOptiFineList | OptiFine 版本列表"
  274. Public Structure DlOptiFineListResult
  275. ''' <summary>
  276. ''' 数据来源名称,如“Official”,“BMCLAPI”。
  277. ''' </summary>
  278. Public SourceName As String
  279. ''' <summary>
  280. ''' 是否为官方的实时数据。
  281. ''' </summary>
  282. Public IsOfficial As Boolean
  283. ''' <summary>
  284. ''' 获取到的数据。
  285. ''' </summary>
  286. Public Value As List(Of DlOptiFineListEntry)
  287. End Structure
  288. Public Class DlOptiFineListEntry
  289. ''' <summary>
  290. ''' 显示名称,已去除 HD_U 字样,如“1.12.2 C8”。
  291. ''' </summary>
  292. Public NameDisplay As String
  293. ''' <summary>
  294. ''' 原始文件名称,如“preview_OptiFine_1.11_HD_U_E1_pre.jar”。
  295. ''' </summary>
  296. Public NameFile As String
  297. ''' <summary>
  298. ''' 对应的版本名称,如“1.13.2-OptiFine_HD_U_E6”。
  299. ''' </summary>
  300. Public NameVersion As String
  301. ''' <summary>
  302. ''' 是否为测试版。
  303. ''' </summary>
  304. Public IsPreview As Boolean
  305. ''' <summary>
  306. ''' 对应的 Minecraft 版本,如“1.12.2”。
  307. ''' </summary>
  308. Public Property Inherit As String
  309. Get
  310. Return _inherit
  311. End Get
  312. Set(value As String)
  313. If value.EndsWith(".0") Then value = Left(value, value.Length - 2)
  314. _inherit = value
  315. End Set
  316. End Property
  317. Private _inherit As String
  318. ''' <summary>
  319. ''' 发布时间,格式为“yyyy/mm/dd”。OptiFine 源无此数据。
  320. ''' </summary>
  321. Public ReleaseTime As String
  322. ''' <summary>
  323. ''' 需要的最低 Forge 版本。空字符串为无限制,“28.1.56” 表示版本号,“1161” 表示版本号的最后一位。
  324. ''' </summary>
  325. Public RequiredForgeVersion As String
  326. End Class
  327. ''' <summary>
  328. ''' OptiFine 版本列表,主加载器。
  329. ''' </summary>
  330. Public DlOptiFineListLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Main", AddressOf DlOptiFineListMain)
  331. Private Sub DlOptiFineListMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
  332. Select Case Setup.Get("ToolDownloadVersion")
  333. Case 0
  334. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)) From {
  335. New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListBmclapiLoader, 30),
  336. New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListOfficialLoader, 60)
  337. }, Loader.IsForceRestarting)
  338. Case 1
  339. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)) From {
  340. New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListOfficialLoader, 5),
  341. New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListBmclapiLoader, 35)
  342. }, Loader.IsForceRestarting)
  343. Case Else
  344. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)) From {
  345. New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListOfficialLoader, 60),
  346. New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListBmclapiLoader, 60)
  347. }, Loader.IsForceRestarting)
  348. End Select
  349. End Sub
  350. ''' <summary>
  351. ''' OptiFine 版本列表,官方源。
  352. ''' </summary>
  353. Public DlOptiFineListOfficialLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Official", AddressOf DlOptiFineListOfficialMain)
  354. Private Sub DlOptiFineListOfficialMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
  355. Dim Result As String = NetGetCodeByClient("https://optifine.net/downloads", Encoding.Default)
  356. If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
  357. '获取所有版本信息
  358. Dim Forge As List(Of String) = RegexSearch(Result, "(?<=colForge'>)[^<]*")
  359. Dim ReleaseTime As List(Of String) = RegexSearch(Result, "(?<=colDate'>)[^<]+")
  360. Dim Name As List(Of String) = RegexSearch(Result, "(?<=OptiFine_)[0-9A-Za-z_.]+(?=.jar"")")
  361. If Not ReleaseTime.Count = Name.Count Then Throw New Exception("版本与发布时间数据无法对应")
  362. If Not Forge.Count = Name.Count Then Throw New Exception("版本与 Forge 兼容数据无法对应")
  363. If ReleaseTime.Count < 10 Then Throw New Exception("获取到的版本数量不足(" & Result & ")")
  364. '转化为列表输出
  365. Dim Versions As New List(Of DlOptiFineListEntry)
  366. For i = 0 To ReleaseTime.Count - 1
  367. Name(i) = Name(i).Replace("_", " ")
  368. Dim Entry As New DlOptiFineListEntry With {
  369. .NameDisplay = Name(i).Replace("HD U ", "").Replace(".0 ", " "),
  370. .ReleaseTime = Join({ReleaseTime(i).Split(".")(2), ReleaseTime(i).Split(".")(1), ReleaseTime(i).Split(".")(0)}, "/"),
  371. .IsPreview = Name(i).ToLower.Contains("pre"),
  372. .Inherit = Name(i).ToString.Split(" ")(0),
  373. .NameFile = If(Name(i).ToLower.Contains("pre"), "preview_", "") & "OptiFine_" & Name(i).Replace(" ", "_") & ".jar",
  374. .RequiredForgeVersion = Forge(i).Replace("Forge ", "").Replace("#", "")}
  375. Entry.NameVersion = Entry.Inherit & "-OptiFine_" & Name(i).ToString.Replace(" ", "_").Replace(Entry.Inherit & "_", "")
  376. Versions.Add(Entry)
  377. Next
  378. Loader.Output = New DlOptiFineListResult With {.IsOfficial = True, .SourceName = "OptiFine 官方源", .Value = Versions}
  379. End Sub
  380. ''' <summary>
  381. ''' OptiFine 版本列表,BMCLAPI。
  382. ''' </summary>
  383. Public DlOptiFineListBmclapiLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Bmclapi", AddressOf DlOptiFineListBmclapiMain)
  384. Private Sub DlOptiFineListBmclapiMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
  385. Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/optifine/versionList",
  386. BackupUrl:="https://download.mcbbs.net/optifine/versionList", IsJson:=True)
  387. Try
  388. Dim Versions As New List(Of DlOptiFineListEntry)
  389. For Each Token As JObject In Json
  390. Dim Entry As New DlOptiFineListEntry With {
  391. .NameDisplay = (Token("mcversion").ToString & Token("type").ToString.Replace("HD_U", "").Replace("_", " ") & " " & Token("patch").ToString).Replace(".0 ", " "),
  392. .ReleaseTime = "",
  393. .IsPreview = Token("patch").ToString.ToLower.Contains("pre"),
  394. .Inherit = Token("mcversion").ToString,
  395. .NameFile = Token("filename").ToString,
  396. .RequiredForgeVersion = If(Token("forge"), "").ToString.Replace("Forge ", "").Replace("#", "")
  397. }
  398. Entry.NameVersion = Entry.Inherit & "-OptiFine_" & (Token("type").ToString & " " & Token("patch").ToString).Replace(".0 ", " ").Replace(" ", "_").Replace(Entry.Inherit & "_", "")
  399. Versions.Add(Entry)
  400. Next
  401. Loader.Output = New DlOptiFineListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Versions}
  402. Catch ex As Exception
  403. Throw New Exception("版本列表解析失败(" & Json.ToString & ")", ex)
  404. End Try
  405. End Sub
  406. #End Region
  407. #Region "DlForgeList | Forge Minecraft 版本列表"
  408. Public Structure DlForgeListResult
  409. ''' <summary>
  410. ''' 数据来源名称,如“Official”,“BMCLAPI”。
  411. ''' </summary>
  412. Public SourceName As String
  413. ''' <summary>
  414. ''' 是否为官方的实时数据。
  415. ''' </summary>
  416. Public IsOfficial As Boolean
  417. ''' <summary>
  418. ''' 获取到的数据。
  419. ''' </summary>
  420. Public Value As List(Of String)
  421. End Structure
  422. ''' <summary>
  423. ''' Forge 版本列表,主加载器。
  424. ''' </summary>
  425. Public DlForgeListLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Main", AddressOf DlForgeListMain)
  426. Private Sub DlForgeListMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
  427. Select Case Setup.Get("ToolDownloadVersion")
  428. Case 0
  429. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)) From {
  430. New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListBmclapiLoader, 30),
  431. New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListOfficialLoader, 60)
  432. }, Loader.IsForceRestarting)
  433. Case 1
  434. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)) From {
  435. New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListOfficialLoader, 5),
  436. New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListBmclapiLoader, 35)
  437. }, Loader.IsForceRestarting)
  438. Case Else
  439. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)) From {
  440. New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListOfficialLoader, 60),
  441. New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListBmclapiLoader, 60)
  442. }, Loader.IsForceRestarting)
  443. End Select
  444. End Sub
  445. ''' <summary>
  446. ''' Forge 版本列表,官方源。
  447. ''' </summary>
  448. Public DlForgeListOfficialLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Official", AddressOf DlForgeListOfficialMain)
  449. Private Sub DlForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
  450. Dim Result As String = NetGetCodeByRequestRetry("http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding.Default, "text/html")
  451. If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
  452. '获取所有版本信息
  453. Dim Names As List(Of String) = RegexSearch(Result, "(?<=a href=""index_)[0-9.]+(_pre[0-9]?)?(?=.html)")
  454. Names.Add("1.2.4") '1.2.4 不会被匹配上
  455. If Names.Count < 10 Then Throw New Exception("获取到的版本数量不足(" & Result & ")")
  456. Loader.Output = New DlForgeListResult With {.IsOfficial = True, .SourceName = "Forge 官方源", .Value = Names}
  457. End Sub
  458. ''' <summary>
  459. ''' Forge 版本列表,BMCLAPI。
  460. ''' </summary>
  461. Public DlForgeListBmclapiLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Bmclapi", AddressOf DlForgeListBmclapiMain)
  462. Private Sub DlForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
  463. Dim Result As String = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft", Encoding.Default,
  464. BackupUrl:="https://download.mcbbs.net/forge/minecraft")
  465. If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
  466. '获取所有版本信息
  467. Dim Names As List(Of String) = RegexSearch(Result, "[0-9.]+(_pre[0-9]?)?")
  468. If Names.Count < 10 Then Throw New Exception("获取到的版本数量不足(" & Result & ")")
  469. Loader.Output = New DlForgeListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Names}
  470. End Sub
  471. #End Region
  472. #Region "DlForgeVersion | Forge 版本列表"
  473. Public Class DlForgeVersionEntry
  474. ''' <summary>
  475. ''' 完整的版本名,如 “14.22.1.2478”。
  476. ''' </summary>
  477. Public Version As String
  478. ''' <summary>
  479. ''' 对应的 Minecraft 版本,如“1.12.2”。
  480. ''' </summary>
  481. Public Inherit As String
  482. ''' <summary>
  483. ''' 发布时间,格式为“yyyy/MM/dd HH:mm”。
  484. ''' </summary>
  485. Public ReleaseTime As String
  486. ''' <summary>
  487. ''' 文件的 MD5 或 SHA1(BMCLAPI 的老版本是 MD5,新版本是 SHA1;官方源总是 MD5)。
  488. ''' </summary>
  489. Public Hash As String = Nothing
  490. ''' <summary>
  491. ''' 是否为推荐版本。
  492. ''' </summary>
  493. Public IsRecommended As Boolean
  494. ''' <summary>
  495. ''' 安装类型。有 installer、client、universal 三种。
  496. ''' </summary>
  497. Public Category As String
  498. ''' <summary>
  499. ''' 版本分支。若无分支则为 Nothing。
  500. ''' </summary>
  501. Public Branch As String = Nothing
  502. ''' <summary>
  503. ''' 是否为新版 Forge。(即为 Minecraft 1.13+)
  504. ''' </summary>
  505. Public ReadOnly Property IsNewType As Boolean
  506. Get
  507. Return Version.Split(".")(0) >= 20
  508. End Get
  509. End Property
  510. ''' <summary>
  511. ''' 构建数。
  512. ''' </summary>
  513. Public ReadOnly Property Build As Integer
  514. Get
  515. Dim Version = Me.Version.Split(".")
  516. If Version(0) < 15 Then
  517. Return Version(Version.Count - 1)
  518. Else
  519. Return Version(Version.Count - 1) + 10000
  520. End If
  521. End Get
  522. End Property
  523. ''' <summary>
  524. ''' 用于下载的文件版本名。可能在 Version 的基础上添加了分支。
  525. ''' </summary>
  526. Public ReadOnly Property FileVersion As String
  527. Get
  528. Return Version & If(Branch Is Nothing, "", "-" & Branch)
  529. End Get
  530. End Property
  531. ''' <summary>
  532. ''' 即将下载的文件全名。
  533. ''' </summary>
  534. Public ReadOnly Property FileName As String
  535. Get
  536. Return "forge-" & Inherit & "-" & FileVersion & "-" & Category & "." & FileSuffix
  537. End Get
  538. End Property
  539. ''' <summary>
  540. ''' 文件扩展名。
  541. ''' </summary>
  542. Public ReadOnly Property FileSuffix As String
  543. Get
  544. If Category = "installer" Then
  545. Return "jar"
  546. Else
  547. Return "zip"
  548. End If
  549. End Get
  550. End Property
  551. End Class
  552. ''' <summary>
  553. ''' Forge 版本列表,主加载器。
  554. ''' </summary>
  555. Public Sub DlForgeVersionMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
  556. Dim DlForgeVersionOfficialLoader As New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion Official", AddressOf DlForgeVersionOfficialMain)
  557. Dim DlForgeVersionBmclapiLoader As New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion Bmclapi", AddressOf DlForgeVersionBmclapiMain)
  558. Select Case Setup.Get("ToolDownloadVersion")
  559. Case 0
  560. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)) From {
  561. New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionBmclapiLoader, 30),
  562. New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionOfficialLoader, 60)
  563. }, Loader.IsForceRestarting)
  564. Case 1
  565. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)) From {
  566. New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionOfficialLoader, 5),
  567. New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionBmclapiLoader, 35)
  568. }, Loader.IsForceRestarting)
  569. Case Else
  570. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)) From {
  571. New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionOfficialLoader, 60),
  572. New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionBmclapiLoader, 60)
  573. }, Loader.IsForceRestarting)
  574. End Select
  575. End Sub
  576. ''' <summary>
  577. ''' Forge 版本列表,官方源。
  578. ''' </summary>
  579. Public Sub DlForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
  580. Dim Result As String
  581. Try
  582. Result = NetGetCodeByDownload("http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" & Loader.Input & ".html")
  583. Catch ex As Exception
  584. If GetExceptionSummary(ex).Contains("(404)") Then
  585. Throw New Exception("没有可用版本")
  586. Else
  587. Throw
  588. End If
  589. End Try
  590. If Result.Length < 1000 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
  591. Dim Versions As New List(Of DlForgeVersionEntry)
  592. Try
  593. '分割版本信息
  594. Dim VersionCodes = Mid(Result, 1, Result.LastIndexOf("</table>")).Replace("<td class=""download-version", "¨").Split("¨")
  595. '获取所有版本信息
  596. For i = 1 To VersionCodes.Count - 1
  597. Dim VersionCode = VersionCodes(i)
  598. Try
  599. '基础信息获取
  600. Dim Name As String = RegexSeek(VersionCode, "(?<=[^(0-9)]+)[0-9\.]+")
  601. Dim IsRecommended As Boolean = VersionCode.Contains("fa promo-recommended")
  602. Dim Inherit As String = Loader.Input
  603. '分支获取
  604. Dim Branch As String = RegexSeek(VersionCode, "(?<=Branch:</strong>[\s]*)[^<]+")
  605. Branch = RegexSeek(If(Branch, ""), "[^\s]+")
  606. If String.IsNullOrWhiteSpace(Branch) Then Branch = Nothing
  607. If Name = "11.15.1.2318" OrElse Name = "11.15.1.1902" OrElse Name = "11.15.1.1890" Then Branch = "1.8.9"
  608. If Branch Is Nothing AndAlso Inherit = "1.7.10" AndAlso Name.Split(".")(3) >= 1300 Then Branch = "1.7.10"
  609. '发布时间获取
  610. Dim ReleaseTimeOriginal = RegexSeek(VersionCode, "(?<=""download-time"" title="")[^""]+")
  611. Dim ReleaseTimeSplit = ReleaseTimeOriginal.Split(" -:".ToCharArray) '原格式:"2021-02-15 03:24:02"
  612. Dim ReleaseDate As New Date(ReleaseTimeSplit(0), ReleaseTimeSplit(1), ReleaseTimeSplit(2), '年月日
  613. ReleaseTimeSplit(3), ReleaseTimeSplit(4), ReleaseTimeSplit(5), '时分秒
  614. 0, DateTimeKind.Utc) '以 UTC 时间作为标准
  615. Dim ReleaseTime As String = ReleaseDate.ToLocalTime.ToString("yyyy/MM/dd HH:mm") '时区与格式转换
  616. '分类与 MD5 获取
  617. Dim MD5 As String, Category As String
  618. If VersionCode.Contains("classifier-installer""") Then
  619. '类型为 installer.jar,支持范围 ~753 (~ 1.6.1 部分), 738~684 (1.5.2 全部)
  620. VersionCode = VersionCode.Substring(VersionCode.IndexOf("installer.jar"))
  621. MD5 = RegexSeek(VersionCode, "(?<=MD5:</strong> )[^<]+")
  622. Category = "installer"
  623. ElseIf VersionCode.Contains("classifier-universal""") Then
  624. '类型为 universal.zip,支持范围 751~449 (1.6.1 部分), 682~183 (1.5.1 ~ 1.3.2 部分)
  625. VersionCode = VersionCode.Substring(VersionCode.IndexOf("universal.zip"))
  626. MD5 = RegexSeek(VersionCode, "(?<=MD5:</strong> )[^<]+")
  627. Category = "universal"
  628. ElseIf VersionCode.Contains("client.zip") Then
  629. '类型为 client.zip,支持范围 182~ (1.3.2 部分 ~)
  630. VersionCode = VersionCode.Substring(VersionCode.IndexOf("client.zip"))
  631. MD5 = RegexSeek(VersionCode, "(?<=MD5:</strong> )[^<]+")
  632. Category = "client"
  633. Else
  634. '没有任何下载(1.6.4 有一部分这种情况)
  635. Continue For
  636. End If
  637. '添加进列表
  638. Versions.Add(New DlForgeVersionEntry With {.Category = Category, .Version = Name, .IsRecommended = IsRecommended, .Hash = MD5.Trim(vbCr, vbLf), .Inherit = Inherit, .ReleaseTime = ReleaseTime, .Branch = Branch})
  639. Catch ex As Exception
  640. Throw New Exception("版本信息提取失败(" & VersionCode & ")", ex)
  641. End Try
  642. Next
  643. Catch ex As Exception
  644. Throw New Exception("版本列表解析失败(" & Result & ")", ex)
  645. End Try
  646. If Versions.Count = 0 Then Throw New Exception("没有可用版本")
  647. Loader.Output = Versions
  648. End Sub
  649. ''' <summary>
  650. ''' Forge 版本列表,BMCLAPI。
  651. ''' </summary>
  652. Public Sub DlForgeVersionBmclapiMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
  653. Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" & Loader.Input,
  654. BackupUrl:="https://download.mcbbs.net/forge/minecraft/" & Loader.Input, IsJson:=True)
  655. Dim Versions As New List(Of DlForgeVersionEntry)
  656. Try
  657. Dim Recommended As String = McDownloadForgeRecommendedGet(Loader.Input)
  658. For Each Token As JObject In Json
  659. '分类与 Hash 获取
  660. Dim Hash As String = Nothing, Category As String = "unknown", Proi As Integer = -1
  661. For Each File As JObject In Token("files")
  662. Select Case File("category").ToString
  663. Case "installer"
  664. If File("format").ToString = "jar" Then
  665. '类型为 installer.jar,支持范围 ~753 (~ 1.6.1 部分), 738~684 (1.5.2 全部)
  666. Hash = File("hash")
  667. Category = "installer"
  668. Proi = 2
  669. End If
  670. Case "universal"
  671. If Proi <= 1 AndAlso File("format").ToString = "zip" Then
  672. '类型为 universal.zip,支持范围 751~449 (1.6.1 部分), 682~183 (1.5.1 ~ 1.3.2 部分)
  673. Hash = File("hash")
  674. Category = "universal"
  675. Proi = 1
  676. End If
  677. Case "client"
  678. If Proi <= 0 AndAlso File("format").ToString = "zip" Then
  679. '类型为 client.zip,支持范围 182~ (1.3.2 部分 ~)
  680. Hash = File("hash")
  681. Category = "client"
  682. Proi = 0
  683. End If
  684. End Select
  685. Next
  686. '获取 Entry
  687. Dim Inherit As String = Loader.Input
  688. Dim Branch As String = Token("branch")
  689. Dim Name As String = Token("version")
  690. If Name = "11.15.1.2318" OrElse Name = "11.15.1.1902" OrElse Name = "11.15.1.1890" Then Branch = "1.8.9"
  691. If Branch Is Nothing AndAlso Inherit = "1.7.10" AndAlso Name.Split(".")(3) >= 1300 Then Branch = "1.7.10"
  692. '基础信息获取
  693. Dim Entry = New DlForgeVersionEntry With {.Hash = Hash, .Category = Category, .Version = Name, .Branch = Branch, .Inherit = Inherit, .IsRecommended = Recommended = Name}
  694. Dim TimeSplit = Token("modified").ToString.Split("-"c, "T"c, ":"c, "."c, " "c, "/"c)
  695. Entry.ReleaseTime = Token("modified").ToObject(Of Date).ToLocalTime.ToString("yyyy/MM/dd HH:mm")
  696. '添加项
  697. Versions.Add(Entry)
  698. Next
  699. Catch ex As Exception
  700. Throw New Exception("版本列表解析失败(" & Json.ToString & ")", ex)
  701. End Try
  702. If Versions.Count = 0 Then Throw New Exception("没有可用版本")
  703. Loader.Output = Versions
  704. End Sub
  705. #End Region
  706. #Region "DlLiteLoaderList | LiteLoader 版本列表"
  707. Public Structure DlLiteLoaderListResult
  708. ''' <summary>
  709. ''' 数据来源名称,如“Official”,“BMCLAPI”。
  710. ''' </summary>
  711. Public SourceName As String
  712. ''' <summary>
  713. ''' 是否为官方的实时数据。
  714. ''' </summary>
  715. Public IsOfficial As Boolean
  716. ''' <summary>
  717. ''' 获取到的数据。
  718. ''' </summary>
  719. Public Value As List(Of DlLiteLoaderListEntry)
  720. ''' <summary>
  721. ''' 官方源的失败原因。若没有则为 Nothing。
  722. ''' </summary>
  723. Public OfficialError As Exception
  724. End Structure
  725. Public Class DlLiteLoaderListEntry
  726. ''' <summary>
  727. ''' 实际的文件名,如“liteloader-installer-1.12-00-SNAPSHOT.jar”。
  728. ''' </summary>
  729. Public FileName As String
  730. ''' <summary>
  731. ''' 是否为测试版。
  732. ''' </summary>
  733. Public IsPreview As Boolean
  734. ''' <summary>
  735. ''' 对应的 Minecraft 版本,如“1.12.2”。
  736. ''' </summary>
  737. Public Inherit As String
  738. ''' <summary>
  739. ''' 是否为 1.7 及更早的远古版。
  740. ''' </summary>
  741. Public IsLegacy As Boolean
  742. ''' <summary>
  743. ''' 发布时间,格式为“yyyy/mm/dd HH:mm”。
  744. ''' </summary>
  745. Public ReleaseTime As String
  746. ''' <summary>
  747. ''' 文件的 MD5。
  748. ''' </summary>
  749. Public MD5 As String
  750. ''' <summary>
  751. ''' 对应的 Json 项。
  752. ''' </summary>
  753. Public JsonToken As JToken
  754. End Class
  755. ''' <summary>
  756. ''' LiteLoader 版本列表,主加载器。
  757. ''' </summary>
  758. Public DlLiteLoaderListLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Main", AddressOf DlLiteLoaderListMain)
  759. Private Sub DlLiteLoaderListMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
  760. Select Case Setup.Get("ToolDownloadVersion")
  761. Case 0
  762. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)) From {
  763. New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListBmclapiLoader, 30),
  764. New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListOfficialLoader, 60)
  765. }, Loader.IsForceRestarting)
  766. Case 1
  767. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)) From {
  768. New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListOfficialLoader, 5),
  769. New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListBmclapiLoader, 35)
  770. }, Loader.IsForceRestarting)
  771. Case Else
  772. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)) From {
  773. New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListOfficialLoader, 60),
  774. New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListBmclapiLoader, 60)
  775. }, Loader.IsForceRestarting)
  776. End Select
  777. End Sub
  778. ''' <summary>
  779. ''' LiteLoader 版本列表,官方源。
  780. ''' </summary>
  781. Public DlLiteLoaderListOfficialLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Official", AddressOf DlLiteLoaderListOfficialMain)
  782. Private Sub DlLiteLoaderListOfficialMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
  783. Dim Result As JObject = NetGetCodeByRequestRetry("http://dl.liteloader.com/versions/versions.json", IsJson:=True)
  784. Try
  785. Dim Json As JObject = Result("versions")
  786. Dim Versions As New List(Of DlLiteLoaderListEntry)
  787. For Each Pair As KeyValuePair(Of String, JToken) In Json
  788. If Pair.Key.StartsWith("1.6") OrElse Pair.Key.StartsWith("1.5") Then Continue For
  789. Dim RealEntry As JToken = If(Pair.Value("artefacts"), Pair.Value("snapshots"))("com.mumfrey:liteloader")("latest")
  790. Versions.Add(New DlLiteLoaderListEntry With {
  791. .Inherit = Pair.Key,
  792. .IsLegacy = Pair.Key.Split(".")(1) < 8,
  793. .IsPreview = RealEntry("stream").ToString.ToLower = "snapshot",
  794. .FileName = "liteloader-installer-" & Pair.Key & If(Pair.Key = "1.8" OrElse Pair.Key = "1.9", ".0", "") & "-00-SNAPSHOT.jar",
  795. .MD5 = RealEntry("md5"),
  796. .ReleaseTime = GetLocalTime(GetDate(RealEntry("timestamp"))).ToString("yyyy/MM/dd HH:mm"),
  797. .JsonToken = RealEntry
  798. })
  799. Next
  800. Loader.Output = New DlLiteLoaderListResult With {.IsOfficial = True, .SourceName = "LiteLoader 官方源", .Value = Versions}
  801. Catch ex As Exception
  802. Throw New Exception("版本列表解析失败(" & Result.ToString & ")", ex)
  803. End Try
  804. End Sub
  805. ''' <summary>
  806. ''' LiteLoader 版本列表,BMCLAPI。
  807. ''' </summary>
  808. Public DlLiteLoaderListBmclapiLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Bmclapi", AddressOf DlLiteLoaderListBmclapiMain)
  809. Private Sub DlLiteLoaderListBmclapiMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
  810. Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json",
  811. BackupUrl:="https://download.mcbbs.net/maven/com/mumfrey/liteloader/versions.json", IsJson:=True)
  812. Try
  813. Dim Json As JObject = Result("versions")
  814. Dim Versions As New List(Of DlLiteLoaderListEntry)
  815. For Each Pair As KeyValuePair(Of String, JToken) In Json
  816. If Pair.Key.StartsWith("1.6") OrElse Pair.Key.StartsWith("1.5") Then Continue For
  817. Dim RealEntry As JToken = If(Pair.Value("artefacts"), Pair.Value("snapshots"))("com.mumfrey:liteloader")("latest")
  818. Versions.Add(New DlLiteLoaderListEntry With {
  819. .Inherit = Pair.Key,
  820. .IsLegacy = Pair.Key.Split(".")(1) < 8,
  821. .IsPreview = RealEntry("stream").ToString.ToLower = "snapshot",
  822. .FileName = "liteloader-installer-" & Pair.Key & If(Pair.Key = "1.8" OrElse Pair.Key = "1.9", ".0", "") & "-00-SNAPSHOT.jar",
  823. .MD5 = RealEntry("md5"),
  824. .ReleaseTime = GetLocalTime(GetDate(RealEntry("timestamp"))).ToString("yyyy/MM/dd HH:mm"),
  825. .JsonToken = RealEntry
  826. })
  827. Next
  828. Loader.Output = New DlLiteLoaderListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Versions}
  829. Catch ex As Exception
  830. Throw New Exception("版本列表解析失败(" & Result.ToString & ")", ex)
  831. End Try
  832. End Sub
  833. #End Region
  834. #Region "DlFabricList | Fabric 列表"
  835. Public Structure DlFabricListResult
  836. ''' <summary>
  837. ''' 数据来源名称,如“Official”,“BMCLAPI”。
  838. ''' </summary>
  839. Public SourceName As String
  840. ''' <summary>
  841. ''' 是否为官方的实时数据。
  842. ''' </summary>
  843. Public IsOfficial As Boolean
  844. ''' <summary>
  845. ''' 获取到的数据。
  846. ''' </summary>
  847. Public Value As JObject
  848. End Structure
  849. ''' <summary>
  850. ''' Fabric 列表,主加载器。
  851. ''' </summary>
  852. Public DlFabricListLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Main", AddressOf DlFabricListMain)
  853. Private Sub DlFabricListMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
  854. Select Case Setup.Get("ToolDownloadVersion")
  855. Case 0
  856. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)) From {
  857. New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListBmclapiLoader, 30),
  858. New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListOfficialLoader, 60)
  859. }, Loader.IsForceRestarting)
  860. Case 1
  861. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)) From {
  862. New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListOfficialLoader, 5),
  863. New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListBmclapiLoader, 35)
  864. }, Loader.IsForceRestarting)
  865. Case Else
  866. DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)) From {
  867. New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListOfficialLoader, 60),
  868. New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListBmclapiLoader, 60)
  869. }, Loader.IsForceRestarting)
  870. End Select
  871. End Sub
  872. ''' <summary>
  873. ''' Fabric 列表,官方源。
  874. ''' </summary>
  875. Public DlFabricListOfficialLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Official", AddressOf DlFabricListOfficialMain)
  876. Private Sub DlFabricListOfficialMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
  877. Dim Result As JObject = NetGetCodeByRequestRetry("https://meta.fabricmc.net/v2/versions", IsJson:=True)
  878. Try
  879. Dim Output = New DlFabricListResult With {.IsOfficial = True, .SourceName = "Fabric 官方源", .Value = Result}
  880. If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
  881. Loader.Output = Output
  882. Catch ex As Exception
  883. Throw New Exception("列表解析失败(" & Result.ToString & ")", ex)
  884. End Try
  885. End Sub
  886. ''' <summary>
  887. ''' Fabric 列表,BMCLAPI。
  888. ''' </summary>
  889. Public DlFabricListBmclapiLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Bmclapi", AddressOf DlFabricListBmclapiMain)
  890. Private Sub DlFabricListBmclapiMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
  891. Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions",
  892. BackupUrl:="https://download.mcbbs.net/fabric-meta/v2/versions", IsJson:=True)
  893. Try
  894. Dim Output = New DlFabricListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Result}
  895. If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
  896. Loader.Output = Output
  897. Catch ex As Exception
  898. Throw New Exception("列表解析失败(" & Result.ToString & ")", ex)
  899. End Try
  900. End Sub
  901. ''' <summary>
  902. ''' Fabric API 列表,官方源。
  903. ''' </summary>
  904. Public DlFabricApiLoader As New LoaderTask(Of Integer, List(Of DlCfFile))("Fabric API List Loader",
  905. Sub(Task As LoaderTask(Of Integer, List(Of DlCfFile))) Task.Output = DlCfGetFiles(306612, False))
  906. ''' <summary>
  907. ''' OptiFabric 列表,官方源。
  908. ''' </summary>
  909. Public DlOptiFabricLoader As New LoaderTask(Of Integer, List(Of DlCfFile))("OptiFabric List Loader",
  910. Sub(Task As LoaderTask(Of Integer, List(Of DlCfFile))) Task.Output = DlCfGetFiles(322385, False))
  911. #End Region
  912. #Region "DlCfProject | CurseForge 工程"
  913. Private DlCfProjectDb As Dictionary(Of String, DlCfDbEntry) = Nothing
  914. Private DlCfProjectCache As New Dictionary(Of Integer, DlCfProject)
  915. ''' <summary>
  916. ''' CurseForge 工程列表获取、搜索请求。
  917. ''' </summary>
  918. Public Class DlCfProjectRequest
  919. Public CategoryId As Integer = 0 '421: Mod(?), 4780: Fabric
  920. Public IsModPack As Boolean = False
  921. Public GameVersion As String = Nothing
  922. Public PageSize As Integer = 50
  923. Public SearchFilter As String = Nothing
  924. Public Index As Integer? = Nothing
  925. Public ModLoader As Integer? = Nothing
  926. Public Function GetAddress() As String
  927. GetAddress = "https://api.curseforge.com/v1/mods/search?gameId=432&sortField=Featured&sortOrder=desc" &
  928. "&pageSize=" & PageSize &
  929. "&categoryId=" & CategoryId &
  930. If(IsModPack, "&classId=4471", "&classId=6") &
  931. If(String.IsNullOrEmpty(GameVersion), "", "&gameVersion=" & GameVersion) &
  932. If(String.IsNullOrEmpty(SearchFilter), "", "&searchFilter=" & Net.WebUtility.UrlEncode(SearchFilter)) &
  933. If(Index IsNot Nothing, "&index=" & Index, "") &
  934. If(ModLoader IsNot Nothing AndAlso ModLoader > 0, "&modLoaderType=" & ModLoader, "")
  935. End Function
  936. #Region "相同判断"
  937. Public Overrides Function Equals(obj As Object) As Boolean
  938. Dim request = TryCast(obj, DlCfProjectRequest)
  939. Return request IsNot Nothing AndAlso request.GetAddress = GetAddress()
  940. End Function
  941. Public Shared Operator =(left As DlCfProjectRequest, right As DlCfProjectRequest) As Boolean
  942. Return EqualityComparer(Of DlCfProjectRequest).Default.Equals(left, right)
  943. End Operator
  944. Public Shared Operator <>(left As DlCfProjectRequest, right As DlCfProjectRequest) As Boolean
  945. Return Not left = right
  946. End Operator
  947. #End Region
  948. End Class
  949. ''' <summary>
  950. ''' CurseForge 工程信息。
  951. ''' </summary>
  952. Public Class DlCfProject
  953. Public Id As Integer
  954. Public Name As String
  955. Public Description As String
  956. Public Website As String
  957. Public DateUpdate As Date
  958. Public DownloadCount As Integer
  959. Public ModLoaders As New List(Of String)
  960. Public GameVersionDesc As String
  961. Private CategoryDesc As List(Of String)
  962. Private Thumb As String
  963. Public IsModPack As Boolean
  964. Public FileIndexes As New List(Of Integer)
  965. Public Files As List(Of DlCfFile)
  966. Private _MCBBS As Integer?
  967. Public Property MCBBS As Integer?
  968. Get
  969. ReleaseCfDatabase()
  970. If _MCBBS Is Nothing Then
  971. If Website IsNot Nothing Then
  972. Dim Keyword As String = Website.TrimEnd("/").Split("/").Last
  973. If DlCfProjectDb.ContainsKey(Keyword) Then _MCBBS = DlCfProjectDb(Keyword).MCBBS
  974. End If
  975. End If
  976. Return _MCBBS
  977. End Get
  978. Set(value As Integer?)
  979. _MCBBS = value
  980. End Set
  981. End Property
  982. Private _ChineseName As String
  983. Public Property ChineseName As String
  984. Get
  985. ReleaseCfDatabase()
  986. If _ChineseName Is Nothing Then
  987. _ChineseName = Name
  988. If Website IsNot Nothing Then
  989. Dim Keyword As String = Website.TrimEnd("/").Split("/").Last
  990. If DlCfProjectDb.ContainsKey(Keyword) Then
  991. Dim Result = DlCfProjectDb(Keyword)
  992. If Not String.IsNullOrEmpty(Result.ChineseName) Then '使用原名
  993. _ChineseName = Result.ChineseName
  994. End If
  995. End If
  996. End If
  997. End If
  998. Return _ChineseName
  999. End Get
  1000. Set(value As String)
  1001. _ChineseName = value
  1002. End Set
  1003. End Property
  1004. Private _WikiId As Integer = -1
  1005. ''' <summary>
  1006. ''' MCMOD 的页面 ID。若为 0 则代表没有对应页面。
  1007. ''' </summary>
  1008. Public Property WikiId As Integer
  1009. Get
  1010. ReleaseCfDatabase()
  1011. If _WikiId = -1 Then
  1012. If Website IsNot Nothing Then
  1013. Dim Keyword As String = Website.TrimEnd("/").Split("/").Last
  1014. If DlCfProjectDb.ContainsKey(Keyword) Then
  1015. _WikiId = DlCfProjectDb(Keyword).WikiId
  1016. Else
  1017. _WikiId = 0
  1018. End If
  1019. End If
  1020. End If
  1021. Return _WikiId
  1022. End Get
  1023. Set(value As Integer)
  1024. _WikiId = value
  1025. End Set
  1026. End Property
  1027. ''' <summary>
  1028. ''' 从 CurseForge 工程 Json 项中初始化实例。若出错会抛出异常。
  1029. ''' </summary>
  1030. Public Sub New(Data As JObject)
  1031. 'https://api.curseforge.com/v1/mods/search?gameId=432&pageSize=5&categoryID=421
  1032. Id = Data("id")
  1033. Name = Data("name")
  1034. Description = Data("summary")
  1035. Website = Data("links")("websiteUrl")
  1036. DateUpdate = Data("dateModified")
  1037. DownloadCount = Data("downloadCount")
  1038. IsModPack = Not Website.Contains("/mc-mods/")
  1039. '获取 Mod Loader 列表
  1040. ModLoaders = New List(Of String)
  1041. For Each File In If(Data("latestFiles"), {})
  1042. Dim NewFile As New DlCfFile(File, IsModPack)
  1043. If Not NewFile.IsAvailable Then Continue For
  1044. ModLoaders.AddRange(NewFile.ModLoaders)
  1045. Next
  1046. ModLoaders = ModLoaders.Distinct.ToList
  1047. '用一个偷懒的方式让 Forge 排在 Fabric 的前面
  1048. ModLoaders = ModLoaders.Select(Function(s) s.Replace("Forge", "Aorge")).ToList()
  1049. ModLoaders.Sort()
  1050. ModLoaders = ModLoaders.Select(Function(s) s.Replace("Aorge", "Forge")).ToList()
  1051. '获取游戏版本
  1052. Dim GameVersions As New List(Of Integer)
  1053. For Each File In If(Data("latestFilesIndexes"), {})
  1054. Dim Version As String = File("gameVersion")
  1055. If Not Version.Contains("1.") Then Continue For
  1056. GameVersions.Add(Version.Split(".")(1).Split("-").First)
  1057. FileIndexes.Add(File("fileId"))
  1058. Next
  1059. GameVersions = Sort(GameVersions.Distinct.ToList, AddressOf VersionSortBoolean)
  1060. If GameVersions.Count = 0 Then
  1061. GameVersionDesc = ""
  1062. Else
  1063. Dim SpaVersions As New List(Of String)
  1064. For i = 0 To GameVersions.Count - 1
  1065. Dim StartVersion As Integer = GameVersions(i), EndVersion As Integer = GameVersions(i)
  1066. For ii = i + 1 To GameVersions.Count - 1
  1067. If GameVersions(ii) = EndVersion - 1 Then
  1068. EndVersion = GameVersions(ii)
  1069. i = ii
  1070. Else
  1071. Exit For
  1072. End If
  1073. Next
  1074. If StartVersion = EndVersion Then
  1075. SpaVersions.Add("1." & StartVersion)
  1076. ElseIf StartVersion >= McVersionHighest Then
  1077. SpaVersions.Add("1." & EndVersion & "+")
  1078. ElseIf EndVersion <= 7 Then
  1079. SpaVersions.Add("1." & StartVersion & "-")
  1080. Exit For
  1081. ElseIf StartVersion - EndVersion = 1 Then
  1082. SpaVersions.Add("1." & StartVersion & ", 1." & EndVersion)
  1083. Else
  1084. SpaVersions.Add("1." & StartVersion & "~1." & EndVersion)
  1085. End If
  1086. Next
  1087. GameVersionDesc = "[" & Join(SpaVersions, ", ") & "] "
  1088. End If
  1089. '获取分类
  1090. Dim Categories = Data("categories")
  1091. Dim CategoryId As New List(Of Integer)
  1092. For Each CategoryToken In Categories
  1093. CategoryId.Add(CategoryToken("id"))
  1094. Next
  1095. CategoryId = ArrayNoDouble(CategoryId)
  1096. CategoryId.Sort()
  1097. CategoryDesc = New List(Of String)
  1098. For Each Category In CategoryId
  1099. Dim Desc As String
  1100. Select Case Category
  1101. Case 406 : Desc = "世界生成"
  1102. Case 407 : Desc = "生物群系"
  1103. Case 409 : Desc = "天然结构"
  1104. Case 410 : Desc = "维度"
  1105. Case 411 : Desc = "生物"
  1106. Case 412 : Desc = "科技"
  1107. Case 414 : Desc = "交通与移动"
  1108. Case 415 : Desc = "管道与物流"
  1109. Case 417 : Desc = "能源"
  1110. Case 4558 : Desc = "红石"
  1111. Case 420 : Desc = "仓储"
  1112. Case 416 : Desc = "农业"
  1113. Case 436 : Desc = "食物"
  1114. Case 419 : Desc = "魔法"
  1115. Case 434 : Desc = "装备与工具"
  1116. Case 422 : Desc = "冒险与探索"
  1117. Case 424 : Desc = "建筑与装饰"
  1118. Case 423 : Desc = "信息显示"
  1119. Case 425 : Desc = "杂项"
  1120. Case 421 : Desc = "支持库"
  1121. Case 435 : Desc = "服务器"
  1122. Case 4471 : Desc = "科幻"
  1123. Case 4483 : Desc = "战斗"
  1124. Case 4477 : Desc = "小游戏"
  1125. Case 4478 : Desc = "任务"
  1126. Case 4484 : Desc = "多人"
  1127. Case 4476 : Desc = "探索"
  1128. Case 4736 : Desc = "空岛"
  1129. Case 4475 : Desc = "冒险"
  1130. Case 4487 : Desc = "FTB"
  1131. Case 4480 : Desc = "基于地图"
  1132. Case 4479 : Desc = "硬核"
  1133. Case 4472 : Desc = "科技"
  1134. Case 4473 : Desc = "魔法"
  1135. Case 4481 : Desc = "小型整合"
  1136. Case 4482 : Desc = "大型整合"
  1137. Case Else : Continue For
  1138. End Select
  1139. CategoryDesc.Add(Desc)
  1140. Next
  1141. If CategoryDesc.Count = 0 Then CategoryDesc.Add("杂类")
  1142. '获取图片
  1143. If Data("logo").Count > 0 Then
  1144. If GetPixelSize(1) > 1.25 Then
  1145. Thumb = Data("logo")("thumbnailUrl").ToString '使用 256x256 图标
  1146. Else
  1147. Thumb = Data("logo")("thumbnailUrl").ToString.Replace("/256/256/", "/64/64/") '使用 64x64 图标
  1148. End If
  1149. End If
  1150. '保存缓存
  1151. If Not DlCfProjectCache.ContainsKey(Id) OrElse DlCfProjectCache(Id).Files Is Nothing Then DictionaryAdd(DlCfProjectCache, Id, Me)
  1152. End Sub
  1153. Public Function ToCfItem(ShowVersionDesc As Boolean, ShowLoaderDesc As Boolean, Optional OnClick As MyCfItem.ClickEventHandler = Nothing) As MyCfItem
  1154. Dim NewItem As New MyCfItem With {.Tag = Me}
  1155. NewItem.LabTitle.Text = ChineseName
  1156. NewItem.LabInfo.Text = Description.Replace(vbCr, "").Replace(vbLf, "")
  1157. NewItem.LabLeft.Text = (If(ModLoaders.Count > 0 AndAlso ShowLoaderDesc, "[" & Join(ModLoaders, "/") & "] ", "") &
  1158. If(ShowVersionDesc, GameVersionDesc, "")).Replace("] [", " ") &
  1159. Join(CategoryDesc, ",") & " (" &
  1160. GetTimeSpanString(DateUpdate - Date.Now) & "更新," &
  1161. If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次下载", DownloadCount & " 次下载") & ")"
  1162. If Thumb Is Nothing Then
  1163. NewItem.Logo = "pack://application:,,,/images/Icons/NoIcon.png"
  1164. Else
  1165. NewItem.Logo = Thumb
  1166. End If
  1167. If OnClick IsNot Nothing Then
  1168. AddHandler NewItem.Click, OnClick
  1169. NewItem.HasAnimation = True
  1170. Else
  1171. NewItem.HasAnimation = False
  1172. End If
  1173. '结束
  1174. Return NewItem
  1175. End Function
  1176. Public Overrides Function Equals(obj As Object) As Boolean
  1177. Dim project = TryCast(obj, DlCfProject)
  1178. Return project IsNot Nothing AndAlso Id = project.Id
  1179. End Function
  1180. Public Shared Operator =(left As DlCfProject, right As DlCfProject) As Boolean
  1181. Return EqualityComparer(Of DlCfProject).Default.Equals(left, right)
  1182. End Operator
  1183. Public Shared Operator <>(left As DlCfProject, right As DlCfProject) As Boolean
  1184. Return Not left = right
  1185. End Operator
  1186. End Class
  1187. Public Class DlCfProjectResult
  1188. Public Projects As List(Of DlCfProject)
  1189. Public Index As Integer
  1190. Public RealCount As Long
  1191. End Class
  1192. ''' <summary>
  1193. ''' CurseForge 工程列表获取事件。
  1194. ''' </summary>
  1195. Public Sub DlCfProjectSub(Task As LoaderTask(Of DlCfProjectRequest, DlCfProjectResult))
  1196. '拒绝 1.13- Quilt(这个版本根本没有 Quilt)
  1197. If Not Task.Input.IsModPack AndAlso
  1198. If(Task.Input.ModLoader, 0) = 5 AndAlso VersionSortInteger(If(Task.Input.GameVersion, "1.15"), "1.14") = -1 Then
  1199. Throw New Exception("Quilt 不支持 Minecraft " & Task.Input.GameVersion)
  1200. End If
  1201. Dim RawFilter As String = If(Task.Input.SearchFilter, "").Trim
  1202. Task.Input.SearchFilter = RawFilter
  1203. RawFilter = RawFilter.ToLower
  1204. Log("[Download] CurseForge 工程列表搜索原始文本:" & RawFilter)
  1205. '中文请求关键字处理
  1206. Dim IsChineseSearch As Boolean = RegexCheck(RawFilter, "[\u4e00-\u9fbb]")
  1207. If IsChineseSearch AndAlso Not String.IsNullOrEmpty(RawFilter) Then
  1208. If Task.Input.IsModPack Then Throw New Exception("整合包搜索仅支持英文")
  1209. ReleaseCfDatabase()
  1210. '构造搜索请求
  1211. Dim SearchEntries As New List(Of SearchEntry(Of DlCfDbEntry))
  1212. For Each Entry In DlCfProjectDb.Values
  1213. If Entry.ChineseName.Contains("动态的树") Then Continue For '傻逼 Mod 的附属太多了
  1214. SearchEntries.Add(New SearchEntry(Of DlCfDbEntry) With {
  1215. .Item = Entry,
  1216. .SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
  1217. New KeyValuePair(Of String, Double)(Entry.ChineseName & Entry.CurseForgeId, 1)
  1218. }
  1219. })
  1220. Next
  1221. '获取搜索结果
  1222. Dim SearchResults = Search(SearchEntries, Task.Input.SearchFilter, 3)
  1223. If SearchResults.Count = 0 Then Throw New Exception("无搜索结果,请尝试搜索英文名称")
  1224. Dim SearchResult As String = ""
  1225. For i = 0 To Math.Min(4, SearchResults.Count - 1) '就算全是准确的,也最多只要 5 个
  1226. If Not SearchResults(i).AbsoluteRight AndAlso i >= Math.Min(2, SearchResults.Count - 1) Then Exit For '把 3 个结果拼合以提高准确度
  1227. SearchResult += SearchResults(i).Item.CurseForgeId.Replace("-", " ") & " "
  1228. SearchResult += SearchResults(i).Item.ChineseName.Replace(" (", "|").Split("|").Last.TrimEnd(") ").Replace(" - ", "§").Split("§").First & " "
  1229. Next
  1230. Log("[Download] CurseForge 中文搜索原始关键词:" & SearchResult, LogLevel.Developer)
  1231. '去除常见连接词
  1232. Dim RealFilter As String = ""
  1233. For Each Word In SearchResult.Split(" ")
  1234. If {"the", "of", "a", "mod"}.Contains(Word.ToLower) OrElse Val(Word) > 0 Then Continue For
  1235. If SearchResult.Split(" ").Count > 3 AndAlso {"ftb"}.Contains(Word.ToLower) Then Continue For
  1236. RealFilter += Word.TrimStart("{[(").TrimEnd("}])") & " "
  1237. Next
  1238. Task.Input.SearchFilter = RealFilter
  1239. Log("[Download] CurseForge 中文搜索最终关键词:" & RealFilter, LogLevel.Developer)
  1240. End If
  1241. '驼峰英文请求关键字处理
  1242. Dim SpacedKeywords = RegexReplace(Task.Input.SearchFilter, "$& ", "([A-Z]+|[a-z]+?)(?=[A-Z]+[a-z]+[a-z ]*)")
  1243. Dim ConnectedKeywords = Task.Input.SearchFilter.Replace(" ", "")
  1244. Dim AllPossibleKeywords = (SpacedKeywords & " " & If(IsChineseSearch, Task.Input.SearchFilter, ConnectedKeywords & " " & RawFilter)).ToLower
  1245. '最终处理关键字:分割、去重
  1246. Dim RightKeywords As New List(Of String)
  1247. For Each Keyword In AllPossibleKeywords.Split(" ")
  1248. If Keyword.Trim = "" Then Continue For
  1249. If {"forge", "fabric", "for", "mod",
  1250. "forge/fabric", "fabric/forge", "fabric/quilt", "quilt/fabric"}.Contains(Keyword) Then 'https://github.com/Hex-Dragon/PCL2/issues/208
  1251. Log("[Download] 已跳过搜索关键词:" & Keyword, LogLevel.Developer)
  1252. Continue For
  1253. End If
  1254. RightKeywords.Add(Keyword)
  1255. Next
  1256. If RawFilter.Length > 0 AndAlso RightKeywords.Count = 0 Then
  1257. Task.Input.SearchFilter = RawFilter '全都被过滤掉了
  1258. Else
  1259. Task.Input.SearchFilter = Join(ArrayNoDouble(RightKeywords), " ").ToLower
  1260. End If
  1261. '例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod)
  1262. If RawFilter.Replace(" ", "").ToLower.Contains("optiforge") Then Task.Input.SearchFilter = "optiforge"
  1263. If RawFilter.Replace(" ", "").ToLower.Contains("optifabric") Then Task.Input.SearchFilter = "optifabric"
  1264. Log("[Download] CurseForge 工程列表搜索最终文本:" & Task.Input.SearchFilter, LogLevel.Developer)
  1265. '正式获取
  1266. Dim Url = Task.Input.GetAddress()
  1267. Log("[Download] 开始获取 CurseForge 工程列表:" & Url)
  1268. Dim RequestResult As JObject = NetGetCodeByRequestRetry(Url, IsJson:=True, Encode:=Encoding.UTF8)
  1269. Dim FileList = GetCfProjectListFromJson(RequestResult("data"), Task.Input.IsModPack)
  1270. If FileList.Count = 0 Then Throw New Exception("无搜索结果")
  1271. '重新排序
  1272. If Not String.IsNullOrEmpty(Task.Input.SearchFilter) Then
  1273. '排序分 = 搜索相对相似度 + 默认排序相对活跃度
  1274. Dim Scores As New Dictionary(Of DlCfProject, Double)
  1275. Dim Entry As New List(Of SearchEntry(Of DlCfProject))
  1276. For i = 1 To FileList.Count
  1277. Dim File = FileList(i - 1)
  1278. Scores.Add(File, (1 - (i / FileList.Count)))
  1279. Entry.Add(New SearchEntry(Of DlCfProject) With {.Item = File, .SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
  1280. New KeyValuePair(Of String, Double)(If(IsChineseSearch, File.ChineseName, File.Name), 1),
  1281. New KeyValuePair(Of String, Double)(File.Description, 0.05)}})
  1282. Next
  1283. Dim SearchResult = Search(Entry, RawFilter, 101, -1)
  1284. For Each OneResult In SearchResult
  1285. Scores(OneResult.Item) += OneResult.Similarity / SearchResult(0).Similarity
  1286. Next
  1287. '根据排序分得出结果
  1288. Dim ResultList = Sort(Scores.ToList, Function(Left As KeyValuePair(Of DlCfProject, Double), Right As KeyValuePair(Of DlCfProject, Double))
  1289. Return Left.Value > Right.Value
  1290. End Function)
  1291. FileList = New List(Of DlCfProject)
  1292. For Each Result In ResultList
  1293. FileList.Add(Result.Key)
  1294. Next
  1295. End If
  1296. Task.Output = New DlCfProjectResult With {.Projects = FileList, .Index = RequestResult("pagination")("index"), .RealCount = RequestResult("pagination")("totalCount")}
  1297. End Sub
  1298. ''' <summary>
  1299. ''' 从 API 返回的 Json 数组中获取工程信息数组。
  1300. ''' </summary>
  1301. Private Function GetCfProjectListFromJson(Json As JArray, IsModPack As Boolean) As List(Of DlCfProject)
  1302. Dim FileList As New List(Of DlCfProject)
  1303. For Each JsonEntry As JObject In Json
  1304. Dim Result = GetCfProjectFromJson(JsonEntry, IsModPack)
  1305. If Result IsNot Nothing Then FileList.Add(Result)
  1306. Next
  1307. Return FileList
  1308. End Function
  1309. ''' <summary>
  1310. ''' 从 API 返回的 Json 中获取工程信息。
  1311. ''' </summary>
  1312. Private Function GetCfProjectFromJson(Json As JObject, IsModPack As Boolean) As DlCfProject
  1313. If Json("links")("websiteUrl") Is Nothing Then
  1314. Log("[Download] 发现关键项为 Nothing 的工程:" & If(Json, "").ToString, LogLevel.Debug)
  1315. Return Nothing
  1316. End If
  1317. If IsModPack = Json("links")("websiteUrl").ToString.Contains("curseforge.com/minecraft/mc-mods") Then
  1318. Log("[Download] 返回的工程与要求的类别不一致:" & Json("links")("websiteUrl").ToString, LogLevel.Debug)
  1319. Return Nothing
  1320. End If
  1321. Return New DlCfProject(Json)
  1322. End Function
  1323. ''' <summary>
  1324. ''' 读取 Mod 数据库。
  1325. ''' </summary>
  1326. Private Sub ReleaseCfDatabase()
  1327. If DlCfProjectDb IsNot Nothing Then Exit Sub
  1328. DlCfProjectDb = New Dictionary(Of String, DlCfDbEntry)
  1329. Dim i As Integer = 0
  1330. For Each Line In DecodeBytes(GetResources("ModData")).Split(vbLf)
  1331. i += 1
  1332. If Line = "" Then Continue For
  1333. For Each EntryData As String In Line.Split("¨")
  1334. Dim Entry = New DlCfDbEntry
  1335. Dim SplitedLine = EntryData.Split("|")
  1336. Entry.CurseForgeId = SplitedLine(0)
  1337. Entry.WikiId = i
  1338. If SplitedLine.Count >= 2 Then
  1339. Entry.ChineseName = SplitedLine(1)
  1340. Entry.MCBBS = If(SplitedLine.Count >= 3, SplitedLine(2), Nothing)
  1341. If Entry.ChineseName.Contains("*") Then '处理 *
  1342. Entry.ChineseName = Entry.ChineseName.Replace("*", " (" &
  1343. String.Join(" ", Entry.CurseForgeId.Split("-").Select(Function(w) w.Substring(0, 1).ToUpper & w.Substring(1, w.Length - 1))) & ")")
  1344. End If
  1345. End If
  1346. DlCfProjectDb(Entry.CurseForgeId) = Entry
  1347. Next
  1348. Next
  1349. End Sub
  1350. Public Class DlCfDbEntry
  1351. ''' <summary>
  1352. ''' McMod 的 ID。
  1353. ''' </summary>
  1354. Public WikiId As Integer
  1355. ''' <summary>
  1356. ''' 中文译名。若为空字符串则使用英文名。
  1357. ''' </summary>
  1358. Public ChineseName As String = ""
  1359. ''' <summary>
  1360. ''' MCBBS 发布帖编号(例如 195107)。若为 Nothing 则没有发布帖。
  1361. ''' </summary>
  1362. Public MCBBS As Integer?
  1363. ''' <summary>
  1364. ''' CurseForge ID(例如 advanced-solar-panels)。
  1365. ''' </summary>
  1366. Public CurseForgeId As String
  1367. Public Overrides Function ToString() As String
  1368. Return CurseForgeId & "|" & WikiId & "|" & ChineseName & If(MCBBS Is Nothing, "", "|" & MCBBS)
  1369. End Function
  1370. End Class
  1371. #End Region
  1372. #Region "DlCfFile | CurseForge Files"
  1373. Public DlCfFileLoader As New LoaderTask(Of KeyValuePair(Of Integer, Boolean), List(Of DlCfFile))("DlCfFile Main", Sub(Task As LoaderTask(Of KeyValuePair(Of Integer, Boolean), List(Of DlCfFile))) Task.Output = DlCfGetFiles(Task.Input.Key, Task.Input.Value))
  1374. ''' <summary>
  1375. ''' CurseForge 工程下的单一版本文件。
  1376. ''' </summary>
  1377. Public Class DlCfFile
  1378. Public IsModPack As Boolean
  1379. Public FileId As Integer
  1380. Public DisplayName As String
  1381. Public [Date] As Date
  1382. Public GameVersion As String()
  1383. Public ModLoaders As New List(Of String)
  1384. Public ReleaseType As Integer
  1385. Public FileName As String
  1386. Public DownloadCount As Integer
  1387. Public Dependencies As New List(Of Integer)
  1388. Private DownloadAddress As String()
  1389. Public ReadOnly Property ReleaseTypeString As String
  1390. Get
  1391. Select Case ReleaseType
  1392. Case 1
  1393. Return "正式版"
  1394. Case 2
  1395. Return If(ModeDebug, "Beta 测试版", "测试版")
  1396. Case Else
  1397. Return If(ModeDebug, "Alpha 测试版", "测试版")
  1398. End Select
  1399. End Get
  1400. End Property
  1401. Public ReadOnly Property IsAvailable As Boolean
  1402. Get
  1403. Return DisplayName IsNot Nothing AndAlso GameVersion IsNot Nothing AndAlso FileName IsNot Nothing AndAlso DownloadAddress(0).StartsWith("http")
  1404. End Get
  1405. End Property
  1406. ''' <summary>
  1407. ''' 从 CurseForge 文件 Json 项中初始化实例。若出错会抛出异常。
  1408. ''' </summary>
  1409. Public Sub New(Data As JObject, IsModPack As Boolean)
  1410. FileId = Data("id")
  1411. DisplayName = Data("displayName").ToString.Replace(" ", "").Trim(" ")
  1412. [Date] = Data("fileDate")
  1413. ReleaseType = Data("releaseType")
  1414. DownloadCount = Data("downloadCount")
  1415. FileName = Data("fileName")
  1416. Me.IsModPack = IsModPack
  1417. '获取下载地址
  1418. Dim Url = Data("downloadUrl").ToString
  1419. If Url = "" Then Url = "https://media.forgecdn.net/files/" & FileId.ToString.Substring(0, 4) & "/" & FileId.ToString.Substring(4).TrimStart("0") & "/" & FileName
  1420. Url = Url.Replace(FileName, Net.WebUtility.UrlEncode(FileName)) '对文件名进行编码
  1421. DownloadAddress = ArrayNoDouble({Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge", "://media"), Url.Replace("-service.overwolf.wtf", ".forgecdn.net"), Url.Replace("://edge", "://media"), Url})
  1422. '获取前置 Mod 的 Addon ID
  1423. If Not IsModPack Then
  1424. For Each Dep In Data("dependencies")
  1425. If Dep("relationType").ToObject(Of Integer) = 3 AndAlso '是一个依赖
  1426. Dep("modId").ToObject(Of Integer) <> 306612 Then '排除 Fabric API
  1427. Dependencies.Add(Dep("modId"))
  1428. End If
  1429. Next
  1430. End If
  1431. '获取游戏版本与 Mod 加载器列表
  1432. Dim Versions As New List(Of String)
  1433. For Each Version In Data("gameVersions").Select(Function(t) t.ToString.Trim.ToLower)
  1434. If Version.StartsWith("1.") OrElse Version.Contains("w") Then
  1435. Versions.Add(Version.Replace("-snapshot", " 快照"))
  1436. ElseIf Version = "forge" OrElse Version = "fabric" OrElse Version = "quilt" OrElse Version = "rift" Then
  1437. ModLoaders.Add(Version.First.ToString.ToUpper & Version.Substring(1))
  1438. End If
  1439. Next
  1440. If Versions.Count > 1 Then
  1441. GameVersion = Sort(Versions, AddressOf VersionSortBoolean).ToArray
  1442. If IsModPack Then GameVersion = {GameVersion(0)}
  1443. ElseIf Versions.Count = 1 Then
  1444. GameVersion = Versions.ToArray
  1445. Else
  1446. GameVersion = {"未知版本"}
  1447. End If
  1448. '用一个偷懒的方式让 Forge 排在 Fabric 的前面
  1449. ModLoaders = ModLoaders.Select(Function(s) s.Replace("Forge", "Aorge")).ToList()
  1450. ModLoaders.Sort()
  1451. ModLoaders = ModLoaders.Select(Function(s) s.Replace("Aorge", "Forge")).ToList()
  1452. End Sub
  1453. ''' <summary>
  1454. ''' 获取对应的下载文件信息。
  1455. ''' </summary>
  1456. ''' <param name="LocalAddress">本地文件夹或文件地址。</param>
  1457. Public Function GetDownloadFile(LocalAddress As String, IsFullPath As Boolean) As NetFile
  1458. Return New NetFile(DownloadAddress, LocalAddress & If(IsFullPath, "", FileName))
  1459. End Function
  1460. Public Function ToListItem(OnClick As MyListItem.ClickEventHandler, Optional OnSaveClick As MyIconButton.ClickEventHandler = Nothing) As MyListItem
  1461. '获取描述信息
  1462. Dim Info As String = ""
  1463. If Not IsModPack Then
  1464. Info += If(ModLoaders.Count > 0, Join(ModLoaders, "/"), "") &
  1465. If(GameVersion.Count > 1, If(ModLoaders.Count > 0, " ", "") & Join(GameVersion, "、").Replace("-snapshot", " 快照"), "")
  1466. If Info <> "" Then Info = "适用于 " & Info & ","
  1467. Info += If(ModeDebug AndAlso Dependencies.Count > 0, Dependencies.Count & " 个前置 Mod,", "")
  1468. End If
  1469. 'If DownloadCount > 0 Then 'DownloadCount 目前的返回内容完全不正确,经常是 0,干脆不显示了
  1470. ' Info += If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次下载,", DownloadCount & " 次下载,")
  1471. 'End If
  1472. Info += GetTimeSpanString([Date] - Date.Now) & "更新"
  1473. Info += If(ReleaseType <> 1, "," & ReleaseTypeString, "")
  1474. '建立控件
  1475. Dim NewItem As New MyListItem With {
  1476. .Title = DisplayName, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Me,
  1477. .Info = Info
  1478. }
  1479. Select Case ReleaseType
  1480. Case 1 'R
  1481. NewItem.Logo = "pack://application:,,,/images/Icons/R.png"
  1482. Case 2 'B
  1483. NewItem.Logo = "pack://application:,,,/images/Icons/B.png"
  1484. Case Else 'A
  1485. NewItem.Logo = "pack://application:,,,/images/Icons/A.png"
  1486. End Select
  1487. AddHandler NewItem.Click, OnClick
  1488. '建立另存为按钮
  1489. If OnSaveClick IsNot Nothing Then
  1490. Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"}
  1491. ToolTipService.SetPlacement(BtnSave, Primitives.PlacementMode.Center)
  1492. ToolTipService.SetVerticalOffset(BtnSave, 30)
  1493. ToolTipService.SetHorizontalOffset(BtnSave, 2)
  1494. AddHandler BtnSave.Click, OnSaveClick
  1495. NewItem.Buttons = {BtnSave}
  1496. NewItem.PaddingRight = 35
  1497. End If
  1498. '结束
  1499. Return NewItem
  1500. End Function
  1501. Public Overrides Function ToString() As String
  1502. Return DisplayName
  1503. End Function
  1504. End Class
  1505. ''' <summary>
  1506. ''' 获取某个 CurseForge 工程下的全部文件列表。
  1507. ''' 必须在工作线程执行,失败会抛出异常。
  1508. ''' </summary>
  1509. Public Function DlCfGetFiles(ProjectId As Integer, IsModPack As Boolean) As List(Of DlCfFile)
  1510. '获取工程对象
  1511. Dim TargetProject As DlCfProject
  1512. If DlCfProjectCache.ContainsKey(ProjectId) Then
  1513. TargetProject = DlCfProjectCache(ProjectId)
  1514. Else
  1515. TargetProject = GetCfProjectFromJson(NetGetCodeByRequestRetry("https://api.curseforge.com/v1/mods/" & ProjectId, IsJson:=True, Encode:=Encoding.UTF8)("data"), IsModPack)
  1516. End If
  1517. '获取工程对象的文件列表
  1518. If TargetProject.Files Is Nothing Then
  1519. Log("[Download] 开始获取 CurseForge 工程 ID 为 " & ProjectId & " 的文件列表")
  1520. Dim Json As JArray
  1521. If TargetProject.IsModPack Then
  1522. '整合包使用全部文件
  1523. Json = NetGetCodeByRequestRetry("https://api.curseforge.com/v1/mods/" & ProjectId & "/files?pageSize=999", Accept:="application/json", IsJson:=True)("data")
  1524. Else
  1525. 'Mod 使用每个版本最新的文件
  1526. Json = GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(TargetProject.FileIndexes, ",") & "]}", "application/json"))("data")
  1527. End If
  1528. Dim Files As New Dictionary(Of Integer, DlCfFile)
  1529. For Each JsonEntry As JObject In Json
  1530. Dim NewFile As New DlCfFile(JsonEntry, IsModPack)
  1531. If NewFile.IsAvailable AndAlso Not Files.ContainsKey(NewFile.FileId) Then Files.Add(NewFile.FileId, NewFile)
  1532. Next
  1533. TargetProject.Files = Files.Values.ToList
  1534. End If
  1535. '获取前置 Mod 列表
  1536. If IsModPack Then GoTo ExitSub
  1537. Dim Deps As New List(Of Integer)
  1538. For Each File In TargetProject.Files
  1539. For Each AddonId In File.Dependencies
  1540. If Not Deps.Contains(AddonId) AndAlso Not DlCfProjectCache.ContainsKey(AddonId) Then Deps.Add(AddonId)
  1541. Next
  1542. Next
  1543. If Deps.Count > 0 Then
  1544. '获取前置 Mod 工程信息
  1545. Log("[Download] 文件列表中需要获取的前置 Mod:" & Join(Deps, ","))
  1546. GetCfProjectListFromJson(GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods", "POST", "{""modIds"": [" & Join(Deps, ",") & "]}", "application/json"))("data"), False)
  1547. End If
  1548. '返回
  1549. ExitSub:
  1550. Return TargetProject.Files
  1551. End Function
  1552. Public Sub DlCfFilesPreload(Stack As StackPanel, Entrys As List(Of DlCfFile), OnClick As MyListItem.ClickEventHandler)
  1553. '获取卡片对应的前置 ID
  1554. '如果为整合包就不会有 Dependencies 信息,所以不用管
  1555. Dim Deps As New List(Of Integer)
  1556. For Each File In Entrys
  1557. For Each AddonId In If(File.Dependencies, New List(Of Integer))
  1558. If Not Deps.Contains(AddonId) Then
  1559. If Not DlCfProjectCache.ContainsKey(AddonId) Then
  1560. Log("[Download] 未找到 ID 为 " & AddonId & " 的前置 Mod 信息", LogLevel.Developer)
  1561. Continue For
  1562. End If
  1563. Deps.Add(AddonId)
  1564. End If
  1565. Next
  1566. Next
  1567. Deps.Remove(306612) '移除 Fabric API
  1568. If Deps.Count = 0 Then Exit Sub
  1569. Deps.Sort()
  1570. '添加开头间隔
  1571. Stack.Children.Add(New TextBlock With {.Text = "前置 Mod", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 2, 0, 5)})
  1572. '添加前置 Mod 列表
  1573. For Each Dep In Deps
  1574. Dim Item = DlCfProjectCache(Dep).ToCfItem(False, True, AddressOf FrmDownloadMod.ProjectClick)
  1575. 'Item.Margin = New Thickness(5, 0, 0, 0)
  1576. Stack.Children.Add(Item)
  1577. Next
  1578. '添加结尾间隔
  1579. Stack.Children.Add(New TextBlock With {.Text = "可选版本", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 12, 0, 5)})
  1580. End Sub
  1581. #End Region
  1582. #Region "DlSource | 镜像下载源"
  1583. Public Function DlSourceResourceGet(MojangBase As String) As String()
  1584. MojangBase = MojangBase.Replace("http://resources.download.minecraft.net", "https://resources.download.minecraft.net")
  1585. Return {MojangBase.Replace("https://resources.download.minecraft.net", "https://download.mcbbs.net/assets"),
  1586. MojangBase,
  1587. MojangBase.Replace("https://resources.download.minecraft.net", "https://bmclapi2.bangbang93.com/assets")
  1588. }
  1589. End Function
  1590. Public Function DlSourceLibraryGet(MojangBase As String) As String()
  1591. Return {MojangBase.Replace("https://libraries.minecraft.net", "https://download.mcbbs.net/maven"),
  1592. MojangBase.Replace("https://libraries.minecraft.net", "https://download.mcbbs.net/libraries"),
  1593. MojangBase,
  1594. MojangBase.Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/maven"),
  1595. MojangBase.Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
  1596. }
  1597. End Function
  1598. Public Function DlSourceLauncherOrMetaGet(MojangBase As String, Optional IsStatic As Boolean = True) As String()
  1599. If MojangBase Is Nothing Then Throw New Exception("无对应的 Json 下载地址")
  1600. If IsStatic Then
  1601. Return {MojangBase.Replace("https://launcher.mojang.com", "https://download.mcbbs.net").Replace("https://launchermeta.mojang.com", "https://download.mcbbs.net"),
  1602. MojangBase,
  1603. MojangBase.Replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com").Replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com")
  1604. }
  1605. Else
  1606. Return {MojangBase.Replace("https://launcher.mojang.com", "https://download.mcbbs.net").Replace("https://launchermeta.mojang.com", "https://download.mcbbs.net"),
  1607. MojangBase,
  1608. MojangBase.Replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com").Replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com")
  1609. }
  1610. End If
  1611. End Function
  1612. Private Sub DlSourceLoader(Of InputType, OutputType)(MainLoader As LoaderTask(Of InputType, OutputType),
  1613. LoaderList As List(Of KeyValuePair(Of LoaderTask(Of InputType, OutputType), Integer)),
  1614. Optional IsForceRestart As Boolean = False)
  1615. Dim WaitCycle As Integer = 0
  1616. Do While True
  1617. '检查加载结束
  1618. Dim IsAllFailed As Boolean = True
  1619. For Each SubLoader In LoaderList
  1620. If WaitCycle = 0 Then
  1621. '如果要强制刷新,就不使用已经加载好的值
  1622. If IsForceRestart Then Exit For
  1623. '如果输入不一样,就不使用已经加载好的值
  1624. If (SubLoader.Key.Input Is Nothing Xor MainLoader.Input Is Nothing) OrElse
  1625. (SubLoader.Key.Input IsNot Nothing AndAlso Not SubLoader.Key.Input.Equals(MainLoader.Input)) Then Continue For
  1626. End If
  1627. If SubLoader.Key.State <> LoadState.Failed Then IsAllFailed = False
  1628. If SubLoader.Key.State = LoadState.Finished Then
  1629. MainLoader.Output = SubLoader.Key.Output
  1630. DlSourceLoaderAbort(LoaderList)
  1631. Exit Sub
  1632. ElseIf IsAllFailed Then
  1633. If WaitCycle < SubLoader.Value * 100 Then WaitCycle = SubLoader.Value * 100
  1634. End If
  1635. '由于 Forge BMCLAPI 没有可用版本导致强制失败
  1636. '在没有可用版本时,官方源会一直卡住,直接使用 BMCLAPI 判定失败即可
  1637. If SubLoader.Key.Error IsNot Nothing AndAlso SubLoader.Key.Error.Message.Contains("没有可用版本") Then
  1638. For Each SubLoader2 In LoaderList
  1639. If WaitCycle < SubLoader2.Value * 100 Then WaitCycle = SubLoader2.Value * 100
  1640. Next
  1641. End If
  1642. Next
  1643. '启动加载源
  1644. If WaitCycle = 0 Then
  1645. '启动第一个源
  1646. LoaderList(0).Key.Start(MainLoader.Input, IsForceRestart)
  1647. '将其他源标记为未启动,以确保可以切换下载源(#184)
  1648. For i = 1 To LoaderList.Count - 1
  1649. LoaderList(i).Key.State = LoadState.Waiting
  1650. Next
  1651. End If
  1652. For i = 0 To LoaderList.Count - 1
  1653. If WaitCycle = LoaderList(i).Value * 100 Then
  1654. If i < LoaderList.Count - 1 Then
  1655. '启动下一个源
  1656. LoaderList(i + 1).Key.Start(MainLoader.Input, IsForceRestart)
  1657. Else
  1658. '失败
  1659. Dim ErrorInfo As Exception = Nothing
  1660. For ii = 0 To LoaderList.Count - 1
  1661. LoaderList(ii).Key.Input = Nothing '重置输入,以免以同样的输入“重试加载”时直接失败
  1662. If LoaderList(ii).Key.Error IsNot Nothing Then
  1663. If ErrorInfo Is Nothing OrElse LoaderList(ii).Key.Error.Message.Contains("没有可用版本") Then
  1664. ErrorInfo = LoaderList(ii).Key.Error
  1665. End If
  1666. End If
  1667. Next
  1668. If ErrorInfo Is Nothing Then ErrorInfo = New TimeoutException("下载源连接超时")
  1669. DlSourceLoaderAbort(LoaderList)
  1670. Throw ErrorInfo
  1671. End If
  1672. Exit For
  1673. End If
  1674. Next
  1675. '计时
  1676. If MainLoader.IsAborted Then
  1677. DlSourceLoaderAbort(LoaderList)
  1678. Exit Sub
  1679. End If
  1680. Thread.Sleep(10)
  1681. WaitCycle += 1
  1682. Loop
  1683. End Sub
  1684. Private Sub DlSourceLoaderAbort(Of InputType, OutputType)(LoaderList As List(Of KeyValuePair(Of LoaderTask(Of InputType, OutputType), Integer)))
  1685. For Each Loader In LoaderList
  1686. If Loader.Key.State = LoadState.Loading Then Loader.Key.Abort()
  1687. Next
  1688. End Sub
  1689. #End Region
  1690. End Module