ModDownloadLib.vb 119 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168
  1. Public Module ModDownloadLib
  2. #Region "Minecraft 下载"
  3. ''' <summary>
  4. ''' 获取下载某个 Minecraft 版本的加载器列表。
  5. ''' 它必须安装到 McFolderSelected,但是可以自定义版本名(不过自定义的版本名不会修改 Json 中的 id 项)。
  6. ''' </summary>
  7. Private Function McDownloadClientLoader(Id As String, Optional JsonUrl As String = Nothing, Optional InstanceName As String = Nothing) As List(Of LoaderBase)
  8. InstanceName = If(InstanceName, Id)
  9. Dim VersionFolder As String = McFolderSelected & "versions\" & InstanceName & "\"
  10. Dim Loaders As New List(Of LoaderBase)
  11. Dim NewInstance As McInstance = Nothing
  12. '下载版本 Json 文件
  13. If JsonUrl Is Nothing Then
  14. Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("获取原版 json 文件下载地址",
  15. Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  16. Dim JsonAddress As String = DlClientListGet(Id)
  17. Task.Output = New List(Of NetFile) From {New NetFile(DlSourceLauncherOrMetaGet(JsonAddress), VersionFolder & InstanceName & ".json")}
  18. End Sub) With {.ProgressWeight = 2, .Show = False})
  19. End If
  20. Loaders.Add(New LoaderDownload(McDownloadClientJsonName, New List(Of NetFile) From {
  21. New NetFile(DlSourceLauncherOrMetaGet(If(JsonUrl, "")), VersionFolder & InstanceName & ".json", New FileChecker With {.IsJson = True})
  22. }) With {.ProgressWeight = 3})
  23. '读取 Json 文件并创建实例
  24. Loaders.Add(New LoaderTask(Of String, String)("读取 json 文件",
  25. Sub(Task As LoaderTask(Of String, String))
  26. NewInstance = New McInstance(VersionFolder)
  27. NewInstance.Load()
  28. End Sub) With {.ProgressWeight = 0, .Show = False})
  29. '下载支持库文件
  30. Dim LoadersLib As New List(Of LoaderBase)
  31. LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析原版支持库文件(副加载器)",
  32. Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  33. Logger.Info($"开始分析原版支持库文件:{VersionFolder}")
  34. Task.Output = McLibNetFilesFromInstance(NewInstance)
  35. End Sub) With {.ProgressWeight = 1, .Show = False})
  36. LoadersLib.Add(New LoaderDownload("下载原版支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 13, .Show = False})
  37. Loaders.Add(New LoaderCombo(Of String)(McDownloadClientLibName, LoadersLib) With {.Block = False, .ProgressWeight = 14})
  38. '下载资源文件
  39. Dim LoadersAssets As New List(Of LoaderBase)
  40. LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析资源文件索引地址(副加载器)",
  41. Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  42. Try
  43. Dim AssetIndex = DlClientAssetIndexGet(NewInstance)
  44. Task.Output = If(AssetIndex Is Nothing, New List(Of NetFile), New List(Of NetFile) From {AssetIndex})
  45. Catch ex As Exception
  46. Throw New Exception("分析资源文件索引地址失败", ex)
  47. End Try
  48. '顺手添加 Json 项目
  49. Try
  50. Dim InstanceJson As JObject = FileUtils.ReadAsJson(VersionFolder & InstanceName & ".json")
  51. InstanceJson.Add("clientVersion", Id)
  52. FileUtils.Write($"{VersionFolder}{InstanceName}.json", InstanceJson.ToString)
  53. Catch ex As Exception
  54. Throw New Exception("添加客户端版本失败", ex)
  55. End Try
  56. End Sub) With {.ProgressWeight = 1, .Show = False})
  57. LoadersAssets.Add(New LoaderDownload("下载资源文件索引(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 3, .Show = False})
  58. LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析所需资源文件(副加载器)",
  59. Sub(Task) Task.Output = McAssetsFixList(NewInstance, True, Task)) With {.ProgressWeight = 0.01, .Show = False})
  60. LoadersAssets.Add(New LoaderDownload("下载资源文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 14, .Show = False})
  61. Loaders.Add(New LoaderCombo(Of String)("下载原版资源文件", LoadersAssets) With {.Block = False, .ProgressWeight = 18})
  62. Return Loaders
  63. End Function
  64. Private Const McDownloadClientLibName As String = "下载原版支持库文件"
  65. Private Const McDownloadClientJsonName As String = "下载原版 json 文件"
  66. #End Region
  67. #Region "Minecraft 下载菜单"
  68. Public Function McDownloadListItem(Entry As JObject, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyVirtualizingElement(Of MyListItem)
  69. Return New MyVirtualizingElement(Of MyListItem)(
  70. Function()
  71. '确定图标
  72. Dim Logo As String
  73. Select Case Entry("type")
  74. Case "release"
  75. Logo = PathImage & "Blocks/Grass.png"
  76. Case "snapshot"
  77. Logo = PathImage & "Blocks/CommandBlock.png"
  78. Case "special"
  79. Logo = PathImage & "Blocks/GoldBlock.png"
  80. Case Else
  81. Logo = PathImage & "Blocks/CobbleStone.png"
  82. End Select
  83. '建立控件
  84. Dim NewItem As New MyListItem With {.Logo = Logo, .SnapsToDevicePixels = True, .Title = Entry("id").ToString, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry}
  85. If Entry("lore") Is Nothing Then
  86. NewItem.Info = Entry("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm")
  87. Else
  88. NewItem.Info = Entry("lore").ToString
  89. End If
  90. If Entry("url").ToString.Contains("pcl") Then NewItem.Info = "[PCL 特供下载] " & NewItem.Info
  91. AddHandler NewItem.Click, OnClick
  92. '建立菜单
  93. If IsSaveOnly Then
  94. NewItem.ContentHandler = AddressOf McDownloadSaveMenuBuild
  95. Else
  96. NewItem.ContentHandler = AddressOf McDownloadMenuBuild
  97. End If
  98. '结束
  99. Return NewItem
  100. End Function) With {.Height = 42}
  101. End Function
  102. Private Sub McDownloadSaveMenuBuild(sender As Object, e As EventArgs)
  103. Dim BtnInfo As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .ToolTip = "更新日志"}
  104. AddHandler BtnInfo.Click, AddressOf McDownloadMenuLog
  105. Dim BtnServer As New MyIconButton With {.LogoScale = 1, .Logo = Logo.IconButtonServer, .ToolTip = "下载服务端"}
  106. AddHandler BtnServer.Click, AddressOf McDownloadMenuSaveServer
  107. sender.Buttons = {BtnServer, BtnInfo}
  108. End Sub
  109. Private Sub McDownloadMenuBuild(sender As Object, e As EventArgs)
  110. Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"}
  111. AddHandler BtnSave.Click, AddressOf McDownloadMenuSave
  112. Dim BtnInfo As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .ToolTip = "更新日志"}
  113. AddHandler BtnInfo.Click, AddressOf McDownloadMenuLog
  114. Dim BtnServer As New MyIconButton With {.LogoScale = 1, .Logo = Logo.IconButtonServer, .ToolTip = "下载服务端"}
  115. AddHandler BtnServer.Click, AddressOf McDownloadMenuSaveServer
  116. sender.Buttons = {BtnSave, BtnInfo, BtnServer}
  117. End Sub
  118. Private Sub McDownloadMenuLog(sender As Object, e As RoutedEventArgs)
  119. Dim Version As JToken
  120. If sender.Tag IsNot Nothing Then
  121. Version = sender.Tag
  122. ElseIf sender.Parent.Tag IsNot Nothing Then
  123. Version = sender.Parent.Tag
  124. Else
  125. Version = sender.Parent.Parent.Tag
  126. End If
  127. McUpdateLogShow(Version)
  128. End Sub
  129. Private Sub McDownloadMenuSaveServer(sender As Object, e As RoutedEventArgs)
  130. Dim Version As MyListItem
  131. If TypeOf sender Is MyListItem Then
  132. Version = sender
  133. ElseIf TypeOf sender.Parent Is MyListItem Then
  134. Version = sender.Parent
  135. Else
  136. Version = sender.Parent.Parent
  137. End If
  138. Try
  139. Dim Id = Version.Title
  140. Dim JsonUrl = Version.Tag("url").ToString
  141. Dim VersionFolder As String = Dialogs.SelectFolder("选择目标文件夹", False).FirstOrDefault
  142. If VersionFolder Is Nothing Then Return
  143. VersionFolder = VersionFolder & Id & "\"
  144. '重复任务检查
  145. For Each OngoingLoader In LoaderTaskbar.ToList()
  146. If OngoingLoader.Name <> $"Minecraft {Id} 服务端下载" Then Continue For
  147. Hint("该服务端正在下载中!", HintType.Red)
  148. Return
  149. Next
  150. Dim Loaders As New List(Of LoaderBase)
  151. '下载版本 JSON 文件
  152. Loaders.Add(New LoaderDownload("下载版本 JSON 文件", New List(Of NetFile) From {
  153. New NetFile(DlSourceLauncherOrMetaGet(JsonUrl), VersionFolder & Id & ".json", New FileChecker With {.IsJson = True})
  154. }) With {.ProgressWeight = 2})
  155. '构建服务端
  156. Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("构建服务端",
  157. Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  158. '分析服务端 JAR 文件下载地址
  159. Dim Instance As New McInstance(VersionFolder)
  160. If Instance.JsonObject("downloads") Is Nothing OrElse Instance.JsonObject("downloads")("server") Is Nothing OrElse Instance.JsonObject("downloads")("server")("url") Is Nothing Then
  161. FileUtils.Delete(VersionFolder & Id & ".json")
  162. If Not DirectoryUtils.GetInfo(VersionFolder).GetFileSystemInfos.Any() Then DirectoryUtils.Delete(VersionFolder)
  163. Task.Output = New List(Of NetFile)
  164. Hint($"Mojang 没有给 Minecraft {Id} 提供官方服务端下载,没法下,撤退!", HintType.Red)
  165. Thread.Sleep(2000) '等玩家把上一个提示看完
  166. Task.Cancel()
  167. Return
  168. End If
  169. Dim JarUrl As String = Instance.JsonObject("downloads")("server")("url")
  170. Dim Checker As New FileChecker With {.MinSize = 1024, .ActualSize = If(Instance.JsonObject("downloads")("server")("size"), -1), .Hash = Instance.JsonObject("downloads")("server")("sha1")}
  171. Task.Output = New List(Of NetFile) From {New NetFile(DlSourceLauncherOrMetaGet(JarUrl), VersionFolder & Id & "-server.jar", Checker)}
  172. '添加启动脚本
  173. Dim Bat As String =
  174. $"@echo off
  175. title {Id} 原版服务端
  176. echo 如果服务端立即停止,请右键编辑该脚本,将下一行开头的 java 替换为适合该 Minecraft 版本的完整 java.exe 的路径。
  177. echo 你可以在 PCL 的 [设置 → 启动选项] 中查看已安装的 java,所需的 java.exe 一般在其中的 bin 文件夹下。
  178. echo ------------------------------
  179. echo 如果提示 ""You need to agree to the EULA in order to run the server"",请打开 eula.txt,按说明阅读并同意 Minecraft EULA 后,将该文件最后一行中的 eula=false 改为 eula=true。
  180. echo ------------------------------
  181. ""java"" -server -XX:+UseG1GC -Xmx6144M -Xms1024M -jar {Id}-server.jar nogui
  182. echo ----------------------
  183. echo 服务端已停止。
  184. pause"
  185. FileUtils.Write(VersionFolder & "Launch Server.bat", Bat,
  186. encoding:=If(Encoding.Default.Equals(Encoding.UTF8), Encoding.UTF8, Encoding.GetEncoding("GB18030")))
  187. '删除版本 JSON
  188. FileUtils.Delete(VersionFolder & Id & ".json")
  189. End Sub) With {.ProgressWeight = 0.5, .Show = False})
  190. '下载服务端文件
  191. Loaders.Add(New LoaderDownload("下载服务端文件", New List(Of NetFile)) With {.ProgressWeight = 5})
  192. '启动
  193. Dim Loader As New LoaderCombo(Of String)("Minecraft " & Id & " 服务端下载", Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
  194. Loader.Start(Id)
  195. LoaderTaskbarAdd(Loader)
  196. FrmMain.BtnExtraDownload.ShowRefresh()
  197. FrmMain.BtnExtraDownload.Ribble()
  198. Catch ex As Exception
  199. Logger.Error(ex, "开始 Minecraft 服务端下载失败")
  200. End Try
  201. End Sub
  202. Public Sub McDownloadMenuSave(sender As Object, e As RoutedEventArgs)
  203. Dim Version As MyListItem
  204. If TypeOf sender Is MyListItem Then
  205. Version = sender
  206. ElseIf TypeOf sender.Parent Is MyListItem Then
  207. Version = sender.Parent
  208. Else
  209. Version = sender.Parent.Parent
  210. End If
  211. Try
  212. Dim Id = Version.Title
  213. Dim JsonUrl = Version.Tag("url").ToString
  214. Dim VersionFolder As String = Dialogs.SelectFolder("选择目标文件夹", False).FirstOrDefault
  215. If VersionFolder Is Nothing Then Return
  216. VersionFolder = VersionFolder & Id & "\"
  217. '重复任务检查
  218. For Each OngoingLoader In LoaderTaskbar.ToList()
  219. If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For
  220. Hint("该版本正在下载中!", HintType.Red)
  221. Return
  222. Next
  223. Dim Loaders As New List(Of LoaderBase)
  224. '下载版本 JSON 文件
  225. Loaders.Add(New LoaderDownload("下载版本 JSON 文件", New List(Of NetFile) From {
  226. New NetFile(DlSourceLauncherOrMetaGet(JsonUrl), VersionFolder & Id & ".json", New FileChecker With {.IsJson = True})
  227. }) With {.ProgressWeight = 2})
  228. '获取支持库文件地址
  229. Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析核心 JAR 文件下载地址",
  230. Sub(Task) Task.Output = New List(Of NetFile) From {DlClientJarGet(New McInstance(VersionFolder), False)}
  231. ) With {.ProgressWeight = 0.5, .Show = False})
  232. '下载支持库文件
  233. Loaders.Add(New LoaderDownload("下载核心 JAR 文件", New List(Of NetFile)) With {.ProgressWeight = 5})
  234. '启动
  235. Dim Loader As New LoaderCombo(Of String)("Minecraft " & Id & " 下载", Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
  236. Loader.Start(Id)
  237. LoaderTaskbarAdd(Loader)
  238. FrmMain.BtnExtraDownload.ShowRefresh()
  239. FrmMain.BtnExtraDownload.Ribble()
  240. Catch ex As Exception
  241. Logger.Error(ex, "开始 Minecraft 下载失败")
  242. End Try
  243. End Sub
  244. ''' <summary>
  245. ''' 显示某 Minecraft 版本的更新日志。
  246. ''' </summary>
  247. ''' <param name="InstanceJson">在 version_manifest.json 中的对应项。</param>
  248. Public Sub McUpdateLogShow(InstanceJson As JToken)
  249. Dim WikiName As String
  250. Dim Id As String = InstanceJson("id").ToString.Lower
  251. If Id = "3d shareware v1.34" Then
  252. WikiName = "3D_Shareware_v1.34"
  253. ElseIf Id = "2.0" Then
  254. WikiName = "Java版2.0"
  255. ElseIf Id = "1.rv-pre1" Then
  256. WikiName = "Java版1.RV-Pre1"
  257. ElseIf Id = "combat test 1" OrElse Id.Contains("combat-1") OrElse Id.Contains("combat-212796") Then
  258. WikiName = "Java版1.14.3_-_Combat_Test"
  259. ElseIf Id = "combat test 2" OrElse Id.Contains("combat-2") OrElse Id.Contains("combat-0") Then
  260. WikiName = "Java版Combat_Test_2"
  261. ElseIf Id = "combat test 3" OrElse Id = "1.14_combat-3" Then
  262. WikiName = "Java版Combat_Test_3"
  263. ElseIf Id = "combat test 4" OrElse Id = "1.15_combat-1" Then
  264. WikiName = "Java版Combat_Test_4"
  265. ElseIf Id = "combat test 5" OrElse Id = "1.15_combat-6" Then
  266. WikiName = "Java版Combat_Test_5"
  267. ElseIf Id = "combat test 6" OrElse Id = "1.16_combat-0" Then
  268. WikiName = "Java版Combat_Test_6"
  269. ElseIf Id = "combat test 7c" OrElse Id = "1.16_combat-3" Then
  270. WikiName = "Java版Combat_Test_7c"
  271. ElseIf Id = "combat test 8b" OrElse Id = "1.16_combat-5" Then
  272. WikiName = "Java版Combat_Test_8b"
  273. ElseIf Id = "combat test 8c" OrElse Id = "1.16_combat-6" Then
  274. WikiName = "Java版Combat_Test_8c"
  275. ElseIf Id = "1.0.0-rc2-2" Then
  276. WikiName = "Java版RC2"
  277. ElseIf Id.StartsWithF("1.19_deep_dark_experimental_snapshot-") OrElse Id.StartsWithF("1_19_deep_dark_experimental_snapshot-") Then
  278. WikiName = Id.Replace("1_19", "1.19").Replace("1.19_deep_dark_experimental_snapshot-", "Java版Deep_Dark_Experimental_Snapshot_")
  279. ElseIf Id = "b1.9-pre6" Then
  280. WikiName = "Java版Beta_1.9_Prerelease_6"
  281. ElseIf Id.Contains("b1.9") Then
  282. WikiName = "Java版Beta_1.9_Prerelease"
  283. ElseIf InstanceJson("type") = "release" OrElse InstanceJson("type") = "snapshot" OrElse InstanceJson("type") = "special" Then
  284. WikiName = If(Id.Contains("w"), "", "Java版") & Id.Replace(" Pre-Release ", "-pre")
  285. ElseIf Id.StartsWithF("b") Then
  286. WikiName = "Java版" & Id.TrimEnd("a", "b", "c", "d", "e").Replace("b", "Beta_")
  287. ElseIf Id.StartsWithF("a") Then
  288. WikiName = "Java版" & Id.TrimEnd("a", "b", "c", "d", "e").Replace("a", "Alpha_v")
  289. ElseIf Id = "inf-20100618" Then
  290. WikiName = "Java版Infdev_20100618"
  291. ElseIf Id = "c0.30_01c" OrElse Id = "c0.30_survival" OrElse Id.Contains("生存测试") Then
  292. WikiName = "Java版Classic_0.30(生存模式)"
  293. ElseIf Id.StartsWithF("c0.31") Then
  294. WikiName = "Java版Indev_0.31_20100130"
  295. ElseIf Id.StartsWithF("c") Then
  296. WikiName = "Java版" & Id.Replace("c", "Classic_")
  297. ElseIf Id.StartsWithF("rd-") Then
  298. WikiName = "Java版Pre-classic_" & Id
  299. Else
  300. Logger.Error($"未知的版本格式:{Id}。")
  301. Return
  302. End If
  303. OpenWebsite("https://zh.minecraft.wiki/w/" & WikiName.Replace("_experimental-snapshot-", "-exp"))
  304. End Sub
  305. #End Region
  306. #Region "OptiFine 下载"
  307. Private Sub McDownloadOptiFineSave(DownloadInfo As DlOptiFineListEntry)
  308. Try
  309. Dim Id As String = DownloadInfo.InstanceName
  310. Dim Target As String = Dialogs.SaveFile("选择保存位置", DownloadInfo.FileName, filter:={("jar", "OptiFine Jar")})
  311. If Target Is Nothing Then Return
  312. '重复任务检查
  313. For Each OngoingLoader In LoaderTaskbar.ToList()
  314. If OngoingLoader.Name <> $"OptiFine {DownloadInfo.DisplayName} 下载" Then Continue For
  315. Hint("该版本正在下载中!", HintType.Red)
  316. Return
  317. Next
  318. Dim Loader As New LoaderCombo(Of DlOptiFineListEntry)("OptiFine " & DownloadInfo.DisplayName & " 下载", McDownloadOptiFineSaveLoader(DownloadInfo, Target)) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
  319. Loader.Start(DownloadInfo)
  320. LoaderTaskbarAdd(Loader)
  321. FrmMain.BtnExtraDownload.ShowRefresh()
  322. FrmMain.BtnExtraDownload.Ribble()
  323. Catch ex As Exception
  324. Logger.Error(ex, "开始 OptiFine 下载失败")
  325. End Try
  326. End Sub
  327. Private Sub McDownloadOptiFineInstall(BaseMcFolderHome As String, Target As String, Task As LoaderTask(Of List(Of NetFile), Boolean), UseJavaWrapper As Boolean)
  328. Dim JavaRange = ValueRange(Of Version).AtLeast(New Version(21, 0)) '默认值
  329. Try
  330. Using Installer = FileUtils.OpenZip(Target)
  331. Dim InstallerClass = Installer.GetEntry("optifine/Installer.class")
  332. If InstallerClass Is Nothing Then Throw New FileNotFoundException("未找到 optifine/Installer.class")
  333. Using Stream = InstallerClass.Open(), Reader As New BinaryReader(Stream)
  334. Dim Header = Reader.ReadBytes(8)
  335. If Header.Length < 8 Then Throw New IOException("Installer.class 长度不足")
  336. If Header(0) <> &HCA OrElse Header(1) <> &HFE OrElse Header(2) <> &HBA OrElse Header(3) <> &HBE Then Throw New InvalidDataException("Installer.class 文件头无效")
  337. Dim ClassVersion = Header(6) * &H100 + Header(7)
  338. If ClassVersion < 49 Then Throw New InvalidDataException("Installer.class 版本过低")
  339. If ClassVersion > 100 Then Throw New InvalidDataException("Installer.class 版本过高")
  340. JavaRange = ValueRange(Of Version).AtLeast(New Version(ClassVersion - 44, 0))
  341. Logger.Info($"OptiFine 安装器 class 版本:{ClassVersion},需要 Java {JavaRange}")
  342. End Using
  343. End Using
  344. Catch ex As Exception
  345. Logger.Warn(ex, "读取 OptiFine 安装器 Java 版本失败")
  346. End Try
  347. '选择 Java
  348. Dim Java As Java = SelectOrDownloadJava(JavaRange, True, Task.CreateCancellationToken, Task.CreateSyncProgressProvider(0, 0.2))
  349. If Task.IsCanceled Then Return
  350. If Java Is Nothing Then Throw New OperationCanceledException
  351. '添加 Java Wrapper 作为主 Jar
  352. Dim Arguments As String
  353. If UseJavaWrapper AndAlso Not Settings.Get(Of Boolean)("LaunchAdvanceDisableJLW") Then
  354. Arguments = $"-Doolloo.jlw.tmpdir=""{PathPure.Value.TrimEnd("\")}"" -Duser.home=""{BaseMcFolderHome.TrimEnd("\")}"" -cp ""{Target}"" -jar ""{LaunchUtils.ExtractPatch("Launch\JavaWrapper.jar", PathPure.Value)}"" optifine.Installer"
  355. Else
  356. Arguments = $"-Duser.home=""{BaseMcFolderHome.TrimEnd("\")}"" -cp ""{Target}"" optifine.Installer"
  357. End If
  358. If Java.Version.Major >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments
  359. '开始启动
  360. SyncLock InstallSyncLock
  361. Dim Info = New ProcessStartInfo With {
  362. .FileName = Java.JavaExePath,
  363. .Arguments = Arguments,
  364. .UseShellExecute = False,
  365. .CreateNoWindow = True,
  366. .RedirectStandardError = True,
  367. .RedirectStandardOutput = True,
  368. .WorkingDirectory = PathUtils.ToShortPath(BaseMcFolderHome)
  369. }
  370. If Info.EnvironmentVariables.ContainsKey("appdata") Then
  371. Info.EnvironmentVariables("appdata") = BaseMcFolderHome
  372. Else
  373. Info.EnvironmentVariables.Add("appdata", BaseMcFolderHome)
  374. End If
  375. Logger.Info($"开始安装 OptiFine:{Target}")
  376. Dim TotalLength As Integer = 0
  377. Dim process As New Process With {.StartInfo = Info}
  378. Dim LastResult As String = ""
  379. Using outputWaitHandle As New AutoResetEvent(False)
  380. Using errorWaitHandle As New AutoResetEvent(False)
  381. AddHandler process.OutputDataReceived,
  382. Function(sender, e)
  383. Try
  384. If e.Data Is Nothing Then
  385. outputWaitHandle.[Set]()
  386. Else
  387. LastResult = e.Data
  388. Logger.Trace(LastResult)
  389. TotalLength += 1
  390. Task.Progress += 0.7 / 7000
  391. End If
  392. Catch ex As ObjectDisposedException
  393. Catch ex As Exception
  394. Logger.Warn(ex, "读取 OptiFine 安装器信息失败")
  395. End Try
  396. Try
  397. If Task.State = LoadState.Canceled AndAlso Not process.HasExited Then
  398. Logger.Info("由于任务取消,已中止 OptiFine 安装")
  399. process.Kill()
  400. End If
  401. Catch
  402. End Try
  403. Return Nothing
  404. End Function
  405. AddHandler process.ErrorDataReceived,
  406. Function(sender, e)
  407. Try
  408. If e.Data Is Nothing Then
  409. errorWaitHandle.[Set]()
  410. Else
  411. LastResult = e.Data
  412. Logger.Trace(LastResult)
  413. TotalLength += 1
  414. Task.Progress += 0.7 / 7000
  415. End If
  416. Catch ex As ObjectDisposedException
  417. Catch ex As Exception
  418. Logger.Warn(ex, "读取 OptiFine 安装器错误信息失败")
  419. End Try
  420. Try
  421. If Task.State = LoadState.Canceled AndAlso Not process.HasExited Then
  422. Logger.Info("由于任务取消,已中止 OptiFine 安装")
  423. process.Kill()
  424. End If
  425. Catch
  426. End Try
  427. Return Nothing
  428. End Function
  429. process.Start()
  430. process.BeginOutputReadLine()
  431. process.BeginErrorReadLine()
  432. '等待
  433. Do Until process.HasExited
  434. Thread.Sleep(10)
  435. Loop
  436. '输出
  437. outputWaitHandle.WaitOne(10000)
  438. errorWaitHandle.WaitOne(10000)
  439. process.Dispose()
  440. If TotalLength < 1000 OrElse LastResult.Contains("at ") Then Throw New Exception("安装器运行出错,末行为 " & LastResult)
  441. End Using
  442. End Using
  443. End SyncLock
  444. End Sub
  445. ''' <summary>
  446. ''' 获取下载某个 OptiFine 版本的加载器列表。
  447. ''' 这不会补全所需的支持库文件,需要后续手动补全支持库。
  448. ''' </summary>
  449. Private Function McDownloadOptiFineLoader(DownloadInfo As DlOptiFineListEntry, McFolder As String, ClientDownloadLoader As LoaderCombo(Of String), ClientFolder As String) As List(Of LoaderBase)
  450. '参数初始化
  451. Dim IsCustomFolder As Boolean = McFolder <> McFolderSelected
  452. Dim Id As String = DownloadInfo.InstanceName
  453. Dim VersionFolder As String = McFolder & "versions\" & Id & "\"
  454. Dim IsNewerVersion As Boolean = DownloadInfo.Inherit.Contains("w") OrElse McVersion.VersionToDrop(DownloadInfo.Inherit) >= 140
  455. Dim Target As String = If(IsNewerVersion,
  456. $"{RequestTaskTempFolder()}OptiFine.jar",
  457. $"{McFolder}libraries\optifine\OptiFine\{DownloadInfo.FileName.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", "")}\{DownloadInfo.FileName.Replace("OptiFine_", "OptiFine-").Replace("preview_", "")}")
  458. Dim Loaders As New List(Of LoaderBase)
  459. '获取下载地址
  460. Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("获取 OptiFine 主文件下载地址",
  461. Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  462. Dim Sources As New List(Of String)
  463. 'BMCLAPI 源
  464. Dim BmclapiInherit As String = DownloadInfo.Inherit
  465. If BmclapiInherit = "1.8" OrElse BmclapiInherit = "1.9" Then BmclapiInherit &= ".0" '#4281
  466. If DownloadInfo.IsPreview Then
  467. Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U_" & DownloadInfo.DisplayName.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
  468. Else
  469. Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U/" & DownloadInfo.DisplayName.Replace(DownloadInfo.Inherit & " ", ""))
  470. End If
  471. '官方源
  472. Dim PageData As String
  473. Try
  474. PageData = NetRequestByClientRetry("https://optifine.net/adloadx?f=" & DownloadInfo.FileName,
  475. Encoding:=New UTF8Encoding(False), Accept:="text/html", SimulateBrowserHeaders:=True)
  476. Task.Progress = 0.8
  477. Sources.Add("https://optifine.net/" & PageData.RegexSearch("downloadx\?f=[^""']+").First)
  478. Logger.Info($"OptiFine {DownloadInfo.DisplayName} 官方下载地址:{Sources.Last}")
  479. Catch ex As Exception
  480. Logger.Warn(ex, $"获取 OptiFine {DownloadInfo.DisplayName} 官方下载地址失败")
  481. End Try
  482. '构造文件请求
  483. Task.Output = New List(Of NetFile) From {New NetFile(Sources.ToArray, Target, New FileChecker With {.MinSize = 300 * 1024})}
  484. End Sub) With {.ProgressWeight = 8})
  485. Loaders.Add(New LoaderDownload("下载 OptiFine 主文件", New List(Of NetFile)) With {.ProgressWeight = 8})
  486. Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("等待原版下载",
  487. Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
  488. '等待原版文件下载完成
  489. Dim TargetLoaders As List(Of LoaderBase) =
  490. ClientDownloadLoader.GetLoaderList.Where(Function(l) l.Name = McDownloadClientLibName OrElse l.Name = McDownloadClientJsonName).
  491. Where(Function(l) l.State <> LoadState.Finished).ToList
  492. If TargetLoaders.Any Then Logger.Info("OptiFine 安装正在等待原版文件下载完成")
  493. Do While TargetLoaders.Any AndAlso Not Task.IsCanceled
  494. TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList
  495. Thread.Sleep(50)
  496. Loop
  497. If Task.IsCanceled Then Return
  498. '复制原版文件
  499. If Not IsCustomFolder Then Return
  500. SyncLock VanillaSyncLock
  501. Dim ClientName As String = PathUtils.GetLastPart(ClientFolder)
  502. DirectoryUtils.Create(McFolder & "versions\" & DownloadInfo.Inherit)
  503. If Not FileUtils.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json") Then
  504. FileUtils.Copy($"{ClientFolder}{ClientName}.json", $"{McFolder}versions\{DownloadInfo.Inherit}\{DownloadInfo.Inherit}.json")
  505. End If
  506. If Not FileUtils.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar") Then
  507. FileUtils.Copy($"{ClientFolder}{ClientName}.jar", $"{McFolder}versions\{DownloadInfo.Inherit}\{DownloadInfo.Inherit}.jar")
  508. End If
  509. End SyncLock
  510. End Sub) With {.ProgressWeight = 0.1, .Show = False})
  511. '安装(新旧方式均需要原版 Jar 和 Json)
  512. If IsNewerVersion Then
  513. Logger.Info($"检测为新版 OptiFine:{DownloadInfo.Inherit}")
  514. Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("安装 OptiFine(方式 A)",
  515. Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
  516. Dim BaseMcFolderHome As String = RequestTaskTempFolder()
  517. Dim BaseMcFolder As String = BaseMcFolderHome & ".minecraft\"
  518. Try
  519. '准备安装环境
  520. If DirectoryUtils.Exists(BaseMcFolder & "versions\" & DownloadInfo.Inherit) Then
  521. DirectoryUtils.Delete(BaseMcFolder & "versions\" & DownloadInfo.Inherit)
  522. End If
  523. DirectoryUtils.Create(BaseMcFolder & "versions\" & DownloadInfo.Inherit & "\")
  524. McFolderLauncherProfilesJsonCreate(BaseMcFolder)
  525. FileUtils.Copy(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json",
  526. BaseMcFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json")
  527. FileUtils.Copy(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar",
  528. BaseMcFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar")
  529. Task.Progress = 0.06
  530. '进行安装
  531. Dim UseJavaWrapper As Boolean = True
  532. Retry:
  533. Try
  534. McDownloadOptiFineInstall(BaseMcFolderHome, Target, Task, UseJavaWrapper)
  535. Catch ex As Exception
  536. If UseJavaWrapper Then
  537. Logger.Warn(ex, "使用 JavaWrapper 安装 OptiFine 失败,将不使用 JavaWrapper 并重试")
  538. UseJavaWrapper = False
  539. GoTo Retry
  540. Else
  541. Throw New Exception("运行 OptiFine 安装器失败", ex)
  542. End If
  543. End Try
  544. Task.Progress = 0.96
  545. '复制文件
  546. FileUtils.Delete(BaseMcFolder & "launcher_profiles.json")
  547. DirectoryUtils.Copy(BaseMcFolder, McFolder)
  548. Task.Progress = 0.98
  549. '清理文件
  550. FileUtils.Delete(Target)
  551. DirectoryUtils.Delete(BaseMcFolderHome)
  552. Catch ex As Exception
  553. Throw New Exception("安装 OptiFine(方式 A)失败", ex)
  554. End Try
  555. End Sub) With {.ProgressWeight = 8})
  556. Else
  557. Logger.Info($"检测为旧版 OptiFine:{DownloadInfo.Inherit}")
  558. Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("安装 OptiFine(方式 B)",
  559. Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
  560. Try
  561. '复制 Jar 文件
  562. FileUtils.Copy(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar", VersionFolder & Id & ".jar")
  563. Task.Progress = 0.7
  564. '建立 Json 文件
  565. Dim InheritInstance As New McInstance(McFolder & "versions\" & DownloadInfo.Inherit)
  566. Dim Json As String = "{
  567. ""id"": """ & Id & """,
  568. ""inheritsFrom"": """ & DownloadInfo.Inherit & """,
  569. ""time"": """ & If(DownloadInfo.ReleaseTime = "", InheritInstance.ReleaseTime.ToString("yyyy'-'MM'-'dd"), DownloadInfo.ReleaseTime.Replace("/", "-")) & "T23:33:33+08:00"",
  570. ""releaseTime"": """ & If(DownloadInfo.ReleaseTime = "", InheritInstance.ReleaseTime.ToString("yyyy'-'MM'-'dd"), DownloadInfo.ReleaseTime.Replace("/", "-")) & "T23:33:33+08:00"",
  571. ""type"": ""release"",
  572. ""libraries"": [
  573. {""name"": ""optifine:OptiFine:" & DownloadInfo.FileName.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", "") & """},
  574. {""name"": ""net.minecraft:launchwrapper:1.12""}
  575. ],
  576. ""mainClass"": ""net.minecraft.launchwrapper.Launch"","
  577. Task.Progress = 0.8
  578. If InheritInstance.IsOldJson Then
  579. '输出旧版 Json 格式
  580. Json += "
  581. ""minimumLauncherVersion"": 18,
  582. ""minecraftArguments"": """ & InheritInstance.JsonObject("minecraftArguments").ToString & " --tweakClass optifine.OptiFineTweaker""
  583. }"
  584. Else
  585. '输出新版 Json 格式
  586. Json += "
  587. ""minimumLauncherVersion"": ""21"",
  588. ""arguments"": {
  589. ""game"": [
  590. ""--tweakClass"",
  591. ""optifine.OptiFineTweaker""
  592. ]
  593. }
  594. }"
  595. End If
  596. FileUtils.Write(VersionFolder & Id & ".json", Json)
  597. Catch ex As Exception
  598. Throw New Exception("安装 OptiFine(方式 B)失败", ex)
  599. End Try
  600. End Sub) With {.ProgressWeight = 1})
  601. End If
  602. Return Loaders
  603. End Function
  604. ''' <summary>
  605. ''' 获取保存某个 OptiFine 版本的加载器列表。
  606. ''' </summary>
  607. Private Function McDownloadOptiFineSaveLoader(DownloadInfo As DlOptiFineListEntry, TargetFolder As String) As List(Of LoaderBase)
  608. Dim Loaders As New List(Of LoaderBase)
  609. '获取下载地址
  610. Loaders.Add(New LoaderTask(Of DlOptiFineListEntry, List(Of NetFile))("获取 OptiFine 下载地址",
  611. Sub(Task As LoaderTask(Of DlOptiFineListEntry, List(Of NetFile)))
  612. Dim Sources As New List(Of String)
  613. 'BMCLAPI 源
  614. Dim BmclapiInherit As String = DownloadInfo.Inherit
  615. If BmclapiInherit = "1.8" OrElse BmclapiInherit = "1.9" Then BmclapiInherit &= ".0" '#4281
  616. If DownloadInfo.IsPreview Then
  617. Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U_" & DownloadInfo.DisplayName.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
  618. Else
  619. Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U/" & DownloadInfo.DisplayName.Replace(DownloadInfo.Inherit & " ", ""))
  620. End If
  621. '官方源
  622. Dim PageData As String
  623. Try
  624. PageData = NetRequestByClientRetry("https://optifine.net/adloadx?f=" & DownloadInfo.FileName,
  625. Encoding:=New UTF8Encoding(False), Accept:="text/html", SimulateBrowserHeaders:=True)
  626. Task.Progress = 0.8
  627. Sources.Add("https://optifine.net/" & PageData.RegexSearch("downloadx\?f=[^""']+").First)
  628. Logger.Info($"OptiFine {DownloadInfo.DisplayName} 官方下载地址:{Sources.Last}")
  629. Catch ex As Exception
  630. Logger.Warn(ex, $"获取 OptiFine {DownloadInfo.DisplayName} 官方下载地址失败")
  631. End Try
  632. Task.Progress = 0.9
  633. '构造文件请求
  634. Task.Output = New List(Of NetFile) From {New NetFile(Sources.ToArray, TargetFolder, New FileChecker With {.MinSize = 64 * 1024})}
  635. End Sub) With {.ProgressWeight = 6})
  636. '下载
  637. Loaders.Add(New LoaderDownload("下载 OptiFine 主文件", New List(Of NetFile)) With {.ProgressWeight = 10, .Block = True})
  638. Return Loaders
  639. End Function
  640. #End Region
  641. #Region "OptiFine 下载菜单"
  642. Public Function OptiFineDownloadListItem(Entry As DlOptiFineListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyVirtualizingElement(Of MyListItem)
  643. Return New MyVirtualizingElement(Of MyListItem)(
  644. Function()
  645. '建立控件
  646. Dim NewItem As New MyListItem With {
  647. .Title = Entry.DisplayName, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
  648. .Info = If(Entry.IsPreview, "测试版", "正式版") &
  649. If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime) &
  650. If(Entry.RequiredForgeVersion Is Nothing, ",不兼容 Forge", If(Entry.RequiredForgeVersion = "", "", ",兼容 Forge " & Entry.RequiredForgeVersion)),
  651. .Logo = PathImage & "Blocks/GrassPath.png"
  652. }
  653. AddHandler NewItem.Click, OnClick
  654. '建立菜单
  655. If IsSaveOnly Then
  656. NewItem.ContentHandler = AddressOf OptiFineSaveContMenuBuild
  657. Else
  658. NewItem.ContentHandler = AddressOf OptiFineContMenuBuild
  659. End If
  660. Return NewItem
  661. End Function) With {.Height = 42}
  662. End Function
  663. Private Sub OptiFineSaveContMenuBuild(sender As Object, e As EventArgs)
  664. Dim BtnInfo As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .ToolTip = "更新日志"}
  665. AddHandler BtnInfo.Click, AddressOf OptiFineLog_Click
  666. sender.Buttons = {BtnInfo}
  667. End Sub
  668. Private Sub OptiFineContMenuBuild(sender As Object, e As EventArgs)
  669. Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"}
  670. AddHandler BtnSave.Click, AddressOf OptiFineSave_Click
  671. Dim BtnInfo As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .ToolTip = "更新日志"}
  672. AddHandler BtnInfo.Click, AddressOf OptiFineLog_Click
  673. sender.Buttons = {BtnSave, BtnInfo}
  674. End Sub
  675. Private Sub OptiFineLog_Click(sender As Object, e As RoutedEventArgs)
  676. Dim Version As DlOptiFineListEntry
  677. If sender.Tag IsNot Nothing Then
  678. Version = sender.Tag
  679. ElseIf sender.Parent.Tag IsNot Nothing Then
  680. Version = sender.Parent.Tag
  681. Else
  682. Version = sender.Parent.Parent.Tag
  683. End If
  684. OpenWebsite("https://optifine.net/changelog?f=" & Version.FileName)
  685. End Sub
  686. Public Sub OptiFineSave_Click(sender As Object, e As RoutedEventArgs)
  687. Dim Version As DlOptiFineListEntry
  688. If sender.Tag IsNot Nothing Then
  689. Version = sender.Tag
  690. ElseIf sender.Parent.Tag IsNot Nothing Then
  691. Version = sender.Parent.Tag
  692. Else
  693. Version = sender.Parent.Parent.Tag
  694. End If
  695. McDownloadOptiFineSave(Version)
  696. End Sub
  697. #End Region
  698. #Region "LiteLoader 下载"
  699. Private Sub McDownloadLiteLoaderSave(DownloadInfo As DlLiteLoaderListEntry)
  700. Try
  701. Dim Id As String = DownloadInfo.Inherit
  702. Dim Target As String = Dialogs.SaveFile("选择保存位置", DownloadInfo.FileName.Replace("-SNAPSHOT", ""), filter:={("jar", "LiteLoader 安装器")})
  703. If Target Is Nothing Then Return
  704. '重复任务检查
  705. For Each OngoingLoader In LoaderTaskbar.ToList()
  706. If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For
  707. Hint("该版本正在下载中!", HintType.Red)
  708. Return
  709. Next
  710. '构造步骤加载器
  711. Dim Loaders As New List(Of LoaderBase)
  712. '下载
  713. Dim Address As New List(Of String)
  714. If DownloadInfo.IsLegacy Then
  715. '老版本
  716. Select Case DownloadInfo.Inherit
  717. Case "1.7.10"
  718. Address.Add("https://dl.liteloader.com/redist/1.7.10/liteloader-installer-1.7.10-04.jar")
  719. Case "1.7.2"
  720. Address.Add("https://dl.liteloader.com/redist/1.7.2/liteloader-installer-1.7.2-04.jar")
  721. Case "1.6.4"
  722. Address.Add("https://dl.liteloader.com/redist/1.6.4/liteloader-installer-1.6.4-01.jar")
  723. Case "1.6.2"
  724. Address.Add("https://dl.liteloader.com/redist/1.6.2/liteloader-installer-1.6.2-04.jar")
  725. Case "1.5.2"
  726. Address.Add("https://dl.liteloader.com/redist/1.5.2/liteloader-installer-1.5.2-01.jar")
  727. Case Else
  728. Throw New NotSupportedException("未知的 Minecraft 版本(" & DownloadInfo.Inherit & ")")
  729. End Select
  730. Else
  731. '官方源
  732. Address.Add("http://jenkins.liteloader.com/job/LiteLoaderInstaller%20" & DownloadInfo.Inherit & "/lastSuccessfulBuild/artifact/" & If(DownloadInfo.Inherit = "1.8", "ant/dist/", "build/libs/") & DownloadInfo.FileName)
  733. End If
  734. Loaders.Add(New LoaderDownload("下载主文件", New List(Of NetFile) From {New NetFile(Address.ToArray, Target, New FileChecker With {.MinSize = 1024 * 1024})}) With {.ProgressWeight = 15})
  735. '启动
  736. Dim Loader As New LoaderCombo(Of DlLiteLoaderListEntry)("LiteLoader " & Id & " 安装器下载", Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
  737. Loader.Start(DownloadInfo)
  738. LoaderTaskbarAdd(Loader)
  739. FrmMain.BtnExtraDownload.ShowRefresh()
  740. FrmMain.BtnExtraDownload.Ribble()
  741. Catch ex As Exception
  742. Logger.Error(ex, "开始 LiteLoader 安装器下载失败")
  743. End Try
  744. End Sub
  745. ''' <summary>
  746. ''' 获取下载某个 LiteLoader 版本的加载器列表。
  747. ''' 这不会补全所需的支持库文件,需要后续手动补全支持库。
  748. ''' </summary>
  749. Private Function McDownloadLiteLoaderLoader(DownloadInfo As DlLiteLoaderListEntry, McFolder As String) As List(Of LoaderBase)
  750. '参数初始化
  751. Dim IsCustomFolder As Boolean = McFolder <> McFolderSelected
  752. Dim Id As String = DownloadInfo.Inherit
  753. Dim Target As String = PathTemp & "Download\" & Id & "-Liteloader.jar"
  754. Dim VersionName As String = DownloadInfo.Inherit & "-LiteLoader"
  755. Dim VersionFolder As String = McFolder & "versions\" & VersionName & "\"
  756. Dim Loaders As New List(Of LoaderBase)
  757. '安装
  758. Loaders.Add(New LoaderTask(Of String, String)("安装 LiteLoader",
  759. Sub(Task As LoaderTask(Of String, String))
  760. Try
  761. '构造版本 Json
  762. Dim VersionJson As New JObject
  763. VersionJson.Add("id", VersionName)
  764. VersionJson.Add("time", Date.ParseExact(DownloadInfo.ReleaseTime, "yyyy/MM/dd HH:mm", Globalization.CultureInfo.InvariantCulture))
  765. VersionJson.Add("releaseTime", Date.ParseExact(DownloadInfo.ReleaseTime, "yyyy/MM/dd HH:mm", Globalization.CultureInfo.InvariantCulture))
  766. VersionJson.Add("type", "release")
  767. VersionJson.Add("arguments", ("{""game"":[""--tweakClass"",""" & DownloadInfo.JsonToken("tweakClass").ToString & """]}").DeserializeJson())
  768. VersionJson.Add("libraries", DownloadInfo.JsonToken("libraries"))
  769. CType(VersionJson("libraries"), JContainer).Add(("{""name"": ""com.mumfrey:liteloader:" & DownloadInfo.JsonToken("version").ToString & """,""url"": ""https://dl.liteloader.com/versions/""}").DeserializeJson())
  770. VersionJson.Add("mainClass", "net.minecraft.launchwrapper.Launch")
  771. VersionJson.Add("minimumLauncherVersion", 18)
  772. VersionJson.Add("inheritsFrom", DownloadInfo.Inherit)
  773. VersionJson.Add("jar", DownloadInfo.Inherit)
  774. '输出 Json 文件,同时会新建该文件夹
  775. FileUtils.Write(VersionFolder & VersionName & ".json", VersionJson.ToString)
  776. Catch ex As Exception
  777. Throw New Exception("安装新 LiteLoader 版本失败", ex)
  778. End Try
  779. End Sub) With {.ProgressWeight = 1})
  780. Return Loaders
  781. End Function
  782. #End Region
  783. #Region "LiteLoader 下载菜单"
  784. Public Function LiteLoaderDownloadListItem(Entry As DlLiteLoaderListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyVirtualizingElement(Of MyListItem)
  785. Return New MyVirtualizingElement(Of MyListItem)(
  786. Function()
  787. '建立控件
  788. Dim NewItem As New MyListItem With {
  789. .Title = Entry.Inherit, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
  790. .Info = If(Entry.IsPreview, "测试版", "稳定版") & If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime),
  791. .Logo = PathImage & "Blocks/Egg.png"
  792. }
  793. AddHandler NewItem.Click, OnClick
  794. '建立菜单
  795. If IsSaveOnly Then
  796. NewItem.ContentHandler = AddressOf LiteLoaderSaveContMenuBuild
  797. Else
  798. NewItem.ContentHandler = AddressOf LiteLoaderContMenuBuild
  799. End If
  800. '结束
  801. Return NewItem
  802. End Function) With {.Height = 42}
  803. End Function
  804. Private Sub LiteLoaderSaveContMenuBuild(sender As MyListItem, e As EventArgs)
  805. If sender.Tag.IsLegacy Then
  806. sender.Buttons = {}
  807. Else
  808. Dim BtnList As New MyIconButton With {.Logo = Logo.IconButtonList, .ToolTip = "查看全部版本", .Tag = sender}
  809. AddHandler BtnList.Click, AddressOf LiteLoaderAll_Click
  810. sender.Buttons = {BtnList}
  811. End If
  812. End Sub
  813. Private Sub LiteLoaderContMenuBuild(sender As MyListItem, e As EventArgs)
  814. Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "保存安装器", .Tag = sender}
  815. AddHandler BtnSave.Click, AddressOf LiteLoaderSave_Click
  816. If sender.Tag.IsLegacy Then
  817. sender.Buttons = {BtnSave}
  818. Else
  819. Dim BtnList As New MyIconButton With {.Logo = Logo.IconButtonList, .ToolTip = "查看全部版本", .Tag = sender}
  820. AddHandler BtnList.Click, AddressOf LiteLoaderAll_Click
  821. sender.Buttons = {BtnSave, BtnList}
  822. End If
  823. End Sub
  824. Private Sub LiteLoaderAll_Click(sender As Object, e As RoutedEventArgs)
  825. Dim Version As DlLiteLoaderListEntry
  826. If TypeOf sender.Tag Is DlLiteLoaderListEntry Then
  827. Version = sender.Tag
  828. Else
  829. Version = sender.Tag.Tag
  830. End If
  831. OpenWebsite("https://jenkins.liteloader.com/view/" & Version.Inherit)
  832. End Sub
  833. Public Sub LiteLoaderSave_Click(sender As Object, e As RoutedEventArgs)
  834. 'ListItem 与小按钮都会调用这个方法
  835. Dim Version As DlLiteLoaderListEntry
  836. If TypeOf sender.Tag Is DlLiteLoaderListEntry Then
  837. Version = sender.Tag
  838. Else
  839. Version = sender.Tag.Tag
  840. End If
  841. McDownloadLiteLoaderSave(Version)
  842. End Sub
  843. #End Region
  844. #Region "Forgelike 下载"
  845. Public Sub McDownloadForgelikeSave(Info As DlForgelikeEntry)
  846. Try
  847. Dim Target As String = Dialogs.SaveFile("选择保存位置", $"{Info.LoaderName}-{Info.Inherit}-{Info.VersionName}.{Info.FileExtension}",
  848. filter:={(Info.FileExtension, $"{Info.LoaderName} 安装器")})
  849. If Target Is Nothing Then Return
  850. Dim DisplayName As String = $"{Info.LoaderName} {Info.Inherit} - {Info.VersionName}"
  851. '重复任务检查
  852. For Each OngoingLoader In LoaderTaskbar.ToList()
  853. If OngoingLoader.Name <> $"{DisplayName} 下载" Then Continue For
  854. Hint("该版本正在下载中!", HintType.Red)
  855. Return
  856. Next
  857. '获取下载地址
  858. Dim Files As New List(Of NetFile)
  859. If Info.IsNeoForge Then
  860. 'NeoForge
  861. Dim NeoForge As DlNeoForgeListEntry = Info
  862. Dim Url As String = NeoForge.UrlBase & "-installer.jar"
  863. Files.Add(New NetFile({
  864. Url.Replace("maven.neoforged.net/releases", "bmclapi2.bangbang93.com/maven"), Url
  865. }, Target, New FileChecker With {.MinSize = 64 * 1024}))
  866. Else
  867. 'Forge
  868. Dim Forge As DlForgeVersionEntry = Info
  869. Files.Add(New NetFile({
  870. $"https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/{Forge.Inherit}-{Forge.FileVersion}/forge-{Forge.Inherit}-{Forge.FileVersion}-{Forge.Category}.{Forge.FileExtension}",
  871. $"https://files.minecraftforge.net/maven/net/minecraftforge/forge/{Forge.Inherit}-{Forge.FileVersion}/forge-{Forge.Inherit}-{Forge.FileVersion}-{Forge.Category}.{Forge.FileExtension}"
  872. }, Target, New FileChecker With {.MinSize = 64 * 1024, .Hash = Forge.Hash}))
  873. End If
  874. '构造加载器
  875. Dim Loaders As New List(Of LoaderBase)
  876. Loaders.Add(New LoaderDownload("下载主文件", Files) With {.ProgressWeight = 6})
  877. '启动
  878. Dim Loader = New LoaderCombo(Of DlForgelikeEntry)(DisplayName & " 下载", Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
  879. Loader.Start(Info)
  880. LoaderTaskbarAdd(Loader)
  881. FrmMain.BtnExtraDownload.ShowRefresh()
  882. FrmMain.BtnExtraDownload.Ribble()
  883. Catch ex As Exception
  884. Logger.Error(ex, $"开始 {Info.LoaderName} 安装器下载失败")
  885. End Try
  886. End Sub
  887. Private Sub ForgelikeInjector(Target As String, Task As LoaderTask(Of Boolean, Boolean), McFolder As String, UseJavaWrapper As Boolean, IsNeoForge As Boolean)
  888. '选择 Java
  889. Dim Java As Java = SelectOrDownloadJava(ValueRange(Of Version).AtLeast(New Version(8, 0, 60)), True, Task.CreateCancellationToken, Task.CreateSyncProgressProvider(0, 0.2))
  890. If Task.IsCanceled Then Return
  891. If Java Is Nothing Then Throw New OperationCanceledException
  892. '添加 Java Wrapper 作为主 Jar
  893. Dim Arguments As String
  894. If UseJavaWrapper AndAlso Not Settings.Get(Of Boolean)("LaunchAdvanceDisableJLW") Then
  895. Arguments = $"-Doolloo.jlw.tmpdir=""{PathPure.Value.TrimEnd("\")}"" -cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" -jar ""{LaunchUtils.ExtractPatch("Launch\JavaWrapper.jar", PathPure.Value)}"" com.bangbang93.ForgeInstaller ""{McFolder}"
  896. Else
  897. Arguments = $"-cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" com.bangbang93.ForgeInstaller ""{McFolder}"
  898. End If
  899. If Java.Version.Major >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments
  900. '开始启动
  901. SyncLock InstallSyncLock
  902. Dim Info = New ProcessStartInfo With {
  903. .FileName = Java.JavaExePath,
  904. .Arguments = Arguments,
  905. .UseShellExecute = False,
  906. .CreateNoWindow = True,
  907. .RedirectStandardError = True,
  908. .RedirectStandardOutput = True
  909. }
  910. Dim LoaderName As String = If(IsNeoForge, "NeoForge", "Forge")
  911. Logger.Info($"开始安装 {LoaderName}:{Arguments}")
  912. Dim process As New Process With {.StartInfo = Info}
  913. Dim LastResults As New Queue(Of String)
  914. Using outputWaitHandle As New AutoResetEvent(False)
  915. Using errorWaitHandle As New AutoResetEvent(False)
  916. AddHandler process.OutputDataReceived,
  917. Function(sender, e)
  918. Try
  919. If e.Data Is Nothing Then
  920. outputWaitHandle.[Set]()
  921. Else
  922. LastResults.Enqueue(e.Data)
  923. If LastResults.Count > 100 Then LastResults.Dequeue()
  924. ForgelikeInjectorLine(e.Data, Task)
  925. End If
  926. Catch ex As ObjectDisposedException
  927. Catch ex As Exception
  928. Logger.Warn(ex, $"读取 {LoaderName} 安装器信息失败")
  929. End Try
  930. Try
  931. If Task.State = LoadState.Canceled AndAlso Not process.HasExited Then
  932. Logger.Info($"由于任务取消,已中止 {LoaderName} 安装")
  933. process.Kill()
  934. End If
  935. Catch
  936. End Try
  937. Return Nothing
  938. End Function
  939. AddHandler process.ErrorDataReceived,
  940. Function(sender, e)
  941. Try
  942. If e.Data Is Nothing Then
  943. errorWaitHandle.[Set]()
  944. Else
  945. LastResults.Enqueue(e.Data)
  946. If LastResults.Count > 100 Then LastResults.Dequeue()
  947. ForgelikeInjectorLine(e.Data, Task)
  948. End If
  949. Catch ex As ObjectDisposedException
  950. Catch ex As Exception
  951. Logger.Warn(ex, $"读取 {LoaderName} 安装器错误信息失败")
  952. End Try
  953. Try
  954. If Task.State = LoadState.Canceled AndAlso Not process.HasExited Then
  955. Logger.Info($"由于任务取消,已中止 {LoaderName} 安装")
  956. process.Kill()
  957. End If
  958. Catch
  959. End Try
  960. Return Nothing
  961. End Function
  962. process.Start()
  963. process.BeginOutputReadLine()
  964. process.BeginErrorReadLine()
  965. '等待
  966. Do Until process.HasExited
  967. Thread.Sleep(10)
  968. Loop
  969. '输出
  970. outputWaitHandle.WaitOne(10000)
  971. errorWaitHandle.WaitOne(10000)
  972. process.Dispose()
  973. '检查是否安装成功:最后 5 行中是否有 true(true 可能在倒数数行,见 #832)
  974. If LastResults.Reverse().Take(5).Any(Function(l) l = "true") Then Return
  975. Logger.Warn(LastResults.Join(vbCrLf))
  976. Dim LastLines As String = ""
  977. For i As Integer = Math.Max(0, LastResults.Count - 5) To LastResults.Count - 1 '最后 5 行
  978. LastLines &= vbCrLf & LastResults(i)
  979. Next
  980. Throw New Exception($"{LoaderName} 安装器出错,日志结束部分为:" & LastLines)
  981. End Using
  982. End Using
  983. End SyncLock
  984. End Sub
  985. Private Sub ForgelikeInjectorLine(Content As String, Task As LoaderTask(Of Boolean, Boolean))
  986. Select Case Content
  987. Case "Extracting json"
  988. Task.Progress = 0.27
  989. Case "Downloading libraries"
  990. Task.Progress = 0.28
  991. Case " File exists: Checksum validated."
  992. Task.Progress += 0.003
  993. Case "Building Processors"
  994. Task.Progress = 0.38
  995. Case "Task: DOWNLOAD_MOJMAPS" 'B
  996. Task.Progress = 0.4
  997. Case "Task: MERGE_MAPPING" 'B
  998. Task.Progress = 0.5
  999. Case "Splitting: "
  1000. Task.Progress = 0.55
  1001. Case "Parameter Annotations" 'B
  1002. Task.Progress = 0.6
  1003. Case "Processing Complete" 'B
  1004. Task.Progress = 0.67
  1005. Case "log: null" 'new
  1006. Task.Progress = 0.67
  1007. Case "Sorting" 'new
  1008. Task.Progress = 0.8
  1009. Case "Remapping final jar" 'A
  1010. Task.Progress = 0.85
  1011. Case "Remapping jar... 50%" 'A
  1012. Task.Progress = 0.9
  1013. Case "Remapping jar... 100%" 'A
  1014. Task.Progress = 0.95
  1015. Case "Injecting profile"
  1016. Task.Progress = 0.98
  1017. Case Else
  1018. Logger.Trace(Content)
  1019. Return
  1020. End Select
  1021. Logger.Info(Content)
  1022. End Sub
  1023. ''' <summary>
  1024. ''' 获取下载某个 Forgelike 版本的加载器列表。
  1025. ''' </summary>
  1026. Private Function McDownloadForgelikeLoader(IsNeoForge As Boolean, LoaderVersion As String, NewInstanceName As String, Inherit As String, Info As DlForgelikeEntry, McFolder As String, ClientDownloadLoader As LoaderCombo(Of String), ClientFolder As String) As List(Of LoaderBase)
  1027. '参数初始化
  1028. McFolder = If(McFolder, McFolderSelected)
  1029. If IsNeoForge AndAlso Info Is Nothing Then
  1030. '需要传入 API Name,但整合包版本可能不以 1.20.1- 开头,所以需要进行特别处理
  1031. If Inherit = "1.20.1" AndAlso Not LoaderVersion.StartsWithF("1.20.1-") Then
  1032. Info = New DlNeoForgeListEntry("1.20.1-" & LoaderVersion)
  1033. Else
  1034. Info = New DlNeoForgeListEntry(LoaderVersion)
  1035. End If
  1036. End If
  1037. If Not IsNeoForge AndAlso LoaderVersion.StartsWithF("1.") AndAlso LoaderVersion.Contains("-") Then
  1038. '类似 1.19.3-41.2.8 格式,优先使用 Version 中要求的版本而非 Inherit(例如 1.19.3 却使用了 1.19 的 Forge)
  1039. Inherit = LoaderVersion.BeforeFirst("-")
  1040. LoaderVersion = LoaderVersion.AfterLast("-")
  1041. End If
  1042. Dim LoaderName As String = If(IsNeoForge, "NeoForge", "Forge")
  1043. Dim IsCustomFolder As Boolean = McFolder <> McFolderSelected
  1044. Dim InstallerAddress As String = RequestTaskTempFolder() & "forge_installer.jar"
  1045. Dim VersionFolder As String = $"{McFolder}versions\{NewInstanceName}\"
  1046. Dim DisplayName As String = $"{LoaderName} {Inherit} - {LoaderVersion}"
  1047. Dim Loaders As New List(Of LoaderBase)
  1048. Dim LibVersionFolder As String = $"{McFolderSelected}versions\{NewInstanceName}\" '作为 Lib 文件目标的版本文件夹
  1049. '获取 Forge 下载信息
  1050. If Info Is Nothing Then
  1051. Loaders.Add(New LoaderTask(Of String, String)($"获取 {LoaderName} 详细信息",
  1052. Sub(Task As LoaderTask(Of String, String))
  1053. '获取 Forge 对应 MC 版本列表
  1054. Dim ForgeLoader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("McDownloadForgeLoader " & Inherit, AddressOf DlForgeVersionMain)
  1055. ForgeLoader.WaitForExit(Inherit)
  1056. Task.Progress = 0.8
  1057. '查找对应版本
  1058. For Each ForgeVersion In ForgeLoader.Output
  1059. If CompareVersion(ForgeVersion.Version.ToString, LoaderVersion) = 0 Then
  1060. Info = ForgeVersion
  1061. Return
  1062. End If
  1063. Next
  1064. Throw New Exception($"未能找到 {LoaderName} " & Inherit & "-" & LoaderVersion & " 的详细信息!")
  1065. End Sub) With {.ProgressWeight = 3})
  1066. End If
  1067. '下载 Forgelike 主文件
  1068. Loaders.Add(New LoaderTask(Of String, List(Of NetFile))($"准备下载 {LoaderName}",
  1069. Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  1070. '添加主文件下载
  1071. Dim Files As New List(Of NetFile)
  1072. If Info.IsNeoForge Then
  1073. 'NeoForge
  1074. Dim Neo As DlNeoForgeListEntry = Info
  1075. Dim Url As String = Neo.UrlBase & "-installer.jar"
  1076. Files.Add(New NetFile({
  1077. Url.Replace("maven.neoforged.net/releases", "bmclapi2.bangbang93.com/maven"), Url
  1078. }, InstallerAddress, New FileChecker With {.MinSize = 64 * 1024}))
  1079. Else
  1080. 'Forge
  1081. Dim Forge As DlForgeVersionEntry = Info
  1082. Dim FileName As String =
  1083. $"{Forge.Inherit.Replace("-", "_")}-{Forge.FileVersion}/forge-{Forge.Inherit.Replace("-", "_")}-{Forge.FileVersion}-{Forge.Category}.{Forge.FileExtension}"
  1084. Files.Add(New NetFile({
  1085. $"https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/{FileName}",
  1086. $"https://files.minecraftforge.net/maven/net/minecraftforge/forge/{FileName}"
  1087. }, InstallerAddress, New FileChecker With {.MinSize = 64 * 1024, .Hash = Forge.Hash}))
  1088. End If
  1089. Task.Output = Files
  1090. End Sub) With {.ProgressWeight = 0.5, .Show = False})
  1091. Loaders.Add(New LoaderDownload($"下载 {LoaderName} 主文件", New List(Of NetFile)) With {.ProgressWeight = 9})
  1092. '安装(仅在新版安装时需要原版 Jar)
  1093. If IsNeoForge OrElse LoaderVersion.BeforeFirst(".") >= 20 Then
  1094. Logger.Info($"检测为{If(IsNeoForge, " Neo", "新版 ")}Forge:{LoaderVersion}")
  1095. Dim Libs As List(Of McLibEntry) = Nothing
  1096. Loaders.Add(New LoaderTask(Of String, List(Of NetFile))($"分析 {LoaderName} 支持库文件",
  1097. Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  1098. Task.Output = New List(Of NetFile)
  1099. Dim Installer As ZipArchive = Nothing
  1100. Try
  1101. '解压并获取、合并两个 Json 的信息
  1102. Installer = FileUtils.OpenZip(InstallerAddress)
  1103. Task.Progress = 0.2
  1104. Dim Json As JObject = Installer.GetEntry("install_profile.json").Open().ReadString().DeserializeJson()
  1105. Dim Json2 As JObject = Installer.GetEntry("version.json").Open().ReadString().DeserializeJson()
  1106. Json.Merge(Json2)
  1107. '获取 Lib 下载信息
  1108. Libs = McLibListGetWithJson(Json, True)
  1109. '添加 Mappings 下载信息
  1110. If Json("data") IsNot Nothing AndAlso Json("data")("MOJMAPS") IsNot Nothing Then
  1111. '下载原版 Json 文件
  1112. Task.Progress = 0.4
  1113. Dim RawJson As JObject = NetRequestByLoader(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True).DeserializeJson()
  1114. '[net.minecraft:client:1.17.1-20210706.113038:mappings@txt] 或 @tsrg]
  1115. Dim OriginalName As String = Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).BeforeFirst("@")
  1116. Dim Address = McLibGet(OriginalName).Replace(".jar", "." & Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).Split("@")(1))
  1117. Dim ClientMappings As JToken = RawJson("downloads")("client_mappings")
  1118. Libs.Add(New McLibEntry With {
  1119. .IsNatives = False, .LocalPath = Address, .OriginalName = OriginalName,
  1120. .Url = ClientMappings("url"), .Size = ClientMappings("size"), .SHA1 = ClientMappings("sha1")})
  1121. Logger.Info($"需要下载 Mappings:{ClientMappings("url")} (SHA1: {ClientMappings("sha1")})")
  1122. End If
  1123. Task.Progress = 0.8
  1124. '去除其中的原始 Forgelike 项
  1125. For i = 0 To Libs.Count - 1
  1126. If Libs(i).LocalPath.EndsWithF($"{LoaderName.Lower}-{Inherit}-{LoaderVersion}.jar") OrElse
  1127. Libs(i).LocalPath.EndsWithF($"{LoaderName.Lower}-{Inherit}-{LoaderVersion}-client.jar") Then
  1128. Logger.Warn($"已从待下载 {LoaderName} 支持库中移除:{Libs(i).LocalPath}")
  1129. Libs.RemoveAt(i)
  1130. Exit For
  1131. End If
  1132. Next
  1133. Task.Output = McLibNetFilesFromTokens(Libs, McFolderSelected)
  1134. Catch ex As Exception
  1135. Throw New Exception($"获取{If(IsNeoForge, " Neo", "新版 ")}Forge 支持库列表失败", ex)
  1136. Finally
  1137. If Installer IsNot Nothing Then Installer.Dispose()
  1138. End Try
  1139. End Sub) With {.ProgressWeight = 1})
  1140. Loaders.Add(New LoaderDownload($"下载 {LoaderName} 支持库文件", New List(Of NetFile)) With {.ProgressWeight = 12})
  1141. Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)($"获取 {LoaderName} 支持库文件",
  1142. Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
  1143. #Region "Forgelike 文件"
  1144. If IsCustomFolder Then
  1145. For Each LibFile As McLibEntry In Libs
  1146. Dim RealPath As String = LibFile.LocalPath.Replace(McFolderSelected, McFolder)
  1147. If Not FileUtils.Exists(RealPath) Then FileUtils.Copy(LibFile.LocalPath, RealPath)
  1148. Logger.Trace($"复制的 {LoaderName} 支持库文件:{LibFile.LocalPath}")
  1149. Next
  1150. End If
  1151. #End Region
  1152. #Region "原版文件"
  1153. '等待原版文件下载完成
  1154. Dim TargetLoaders As List(Of LoaderBase) =
  1155. ClientDownloadLoader.GetLoaderList.Where(Function(l) l.Name = McDownloadClientLibName OrElse l.Name = McDownloadClientJsonName).
  1156. Where(Function(l) l.State <> LoadState.Finished).ToList()
  1157. If TargetLoaders.Any Then Logger.Info($"{LoaderName} 安装正在等待原版文件下载完成")
  1158. Do While TargetLoaders.Any AndAlso Not Task.IsCanceled
  1159. TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList
  1160. Thread.Sleep(50)
  1161. Loop
  1162. If Task.IsCanceled Then Return
  1163. '复制原版文件
  1164. If Not IsCustomFolder Then Return
  1165. SyncLock VanillaSyncLock
  1166. Dim ClientName As String = PathUtils.GetLastPart(ClientFolder)
  1167. DirectoryUtils.Create(McFolder & "versions\" & Inherit)
  1168. If Not FileUtils.Exists(McFolder & "versions\" & Inherit & "\" & Inherit & ".json") Then
  1169. FileUtils.Copy(ClientFolder & ClientName & ".json", McFolder & "versions\" & Inherit & "\" & Inherit & ".json")
  1170. End If
  1171. If Not FileUtils.Exists(McFolder & "versions\" & Inherit & "\" & Inherit & ".jar") Then
  1172. FileUtils.Copy(ClientFolder & ClientName & ".jar", McFolder & "versions\" & Inherit & "\" & Inherit & ".jar")
  1173. End If
  1174. End SyncLock
  1175. #End Region
  1176. End Sub) With {.ProgressWeight = 0.1, .Show = False})
  1177. Loaders.Add(New LoaderTask(Of Boolean, Boolean)(If(IsNeoForge, "安装 NeoForge", "安装 Forge(方式 A)"),
  1178. Sub(Task As LoaderTask(Of Boolean, Boolean))
  1179. Dim Installer As ZipArchive = Nothing
  1180. Try
  1181. Logger.Info($"开始进行 Forgelike 安装:{InstallerAddress}")
  1182. '记录当前文件夹列表(在新建目标文件夹之前)
  1183. Dim OldList = DirectoryUtils.EnumerateDirectories(McFolder & "versions\").ToList()
  1184. '解压并获取信息
  1185. Installer = FileUtils.OpenZip(InstallerAddress)
  1186. Dim Json As JObject = Installer.GetEntry("install_profile.json").Open().ReadString().DeserializeJson()
  1187. '新建目标版本文件夹
  1188. DirectoryUtils.Create(VersionFolder)
  1189. Task.Progress = 0.04
  1190. '释放 launcher_installer.json
  1191. McFolderLauncherProfilesJsonCreate(McFolder)
  1192. Task.Progress = 0.05
  1193. '运行 Forge 安装器
  1194. Dim UseJavaWrapper As Boolean = True
  1195. Retry:
  1196. Try
  1197. '释放 Forge 注入器
  1198. ExtractResources(PathTemp & "Cache\forge_installer.jar", "ForgeInstaller")
  1199. Task.Progress = 0.06
  1200. '运行注入器
  1201. ForgelikeInjector(InstallerAddress, Task, McFolder, UseJavaWrapper, IsNeoForge)
  1202. Task.Progress = 0.97
  1203. Catch ex As Exception
  1204. If UseJavaWrapper Then
  1205. Logger.Warn(ex, $"使用 JavaWrapper 安装 {LoaderName} 失败,将不使用 JavaWrapper 并重试")
  1206. UseJavaWrapper = False
  1207. GoTo Retry
  1208. Else
  1209. Throw New Exception($"运行 {LoaderName} 安装器失败", ex)
  1210. End If
  1211. End Try
  1212. '复制新增的版本 Json
  1213. Dim DeltaList = DirectoryUtils.EnumerateDirectories(McFolder & "versions\").
  1214. SkipWhile(Function(i) OldList.Contains(i)).Select(Function(f) DirectoryUtils.GetInfo(f)).ToList()
  1215. If DeltaList.Count > 1 Then
  1216. '它可能和 OptiFine 安装同时运行,导致增加的文件不止一个(这导致了 #151)
  1217. '也可能是因为 Forge 安装器的 Bug,生成了一个名字错误的文件夹,所以需要检查文件夹是否为空
  1218. DeltaList = DeltaList.Where(Function(l) l.Name.ContainsIgnoreCase("forge") AndAlso l.EnumerateFiles.Any).ToList
  1219. End If
  1220. If DeltaList.Count = 1 Then
  1221. '如果没有新增文件夹,那么预测的文件夹名就是正确的
  1222. '如果只新增 1 个文件夹,那么复制 json 文件
  1223. Dim JsonFile As FileInfo = DeltaList(0).EnumerateFiles.First()
  1224. FileUtils.Copy(JsonFile.FullName, VersionFolder & NewInstanceName & ".json")
  1225. Logger.Info($"已复制新增的版本 JSON 文件:{JsonFile.FullName} -> {VersionFolder}{NewInstanceName}.json")
  1226. ElseIf DeltaList.Count > 1 Then
  1227. '新增了多个文件夹
  1228. Logger.Info($"有多个疑似的新增版本,无法确定:{DeltaList.Select(Function(d) d.Name).Join(";")}")
  1229. Else
  1230. '没有新增文件夹
  1231. Logger.Info("未找到新增的版本文件夹")
  1232. End If
  1233. Catch ex As Exception
  1234. Throw New Exception($"安装新 {LoaderName} 版本失败", ex)
  1235. Finally
  1236. '清理文件
  1237. Try
  1238. Installer?.Dispose()
  1239. FileUtils.Delete(InstallerAddress)
  1240. Catch ex As Exception
  1241. Logger.Warn(ex, $"安装 {LoaderName} 清理文件时出错")
  1242. End Try
  1243. End Try
  1244. End Sub) With {.ProgressWeight = 10})
  1245. Else
  1246. Logger.Info($"检测为非新版 Forge:{LoaderVersion}")
  1247. Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)($"安装 {LoaderName}(方式 B)",
  1248. Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
  1249. Dim Installer As ZipArchive = Nothing
  1250. Try
  1251. '解压并获取信息
  1252. Installer = FileUtils.OpenZip(InstallerAddress)
  1253. Task.Progress = 0.2
  1254. Dim Json As JObject = Installer.GetEntry("install_profile.json").Open().ReadString().DeserializeJson()
  1255. Task.Progress = 0.4
  1256. '新建版本文件夹
  1257. DirectoryUtils.Create(VersionFolder)
  1258. Task.Progress = 0.5
  1259. If Json("install") Is Nothing Then
  1260. '中版:Legacy 方式 1
  1261. Logger.Info($"开始进行 Forge 安装,Legacy 方式 1:{InstallerAddress}")
  1262. '建立 Json 文件
  1263. Dim JsonVersion As JObject = Installer.GetEntry(Json("json").ToString.TrimStart("/")).Open().ReadString().DeserializeJson()
  1264. JsonVersion("id") = NewInstanceName
  1265. FileUtils.Write(VersionFolder & NewInstanceName & ".json", JsonVersion.ToString)
  1266. Task.Progress = 0.6
  1267. '解压支持库文件
  1268. Installer.Dispose()
  1269. FileUtils.ExtractToDirectory(InstallerAddress, InstallerAddress & "_unrar\")
  1270. If DirectoryUtils.Exists(InstallerAddress & "_unrar\maven\") Then DirectoryUtils.Copy(InstallerAddress & "_unrar\maven\", McFolder & "libraries\")
  1271. DirectoryUtils.Delete(InstallerAddress & "_unrar\")
  1272. Else
  1273. '旧版:Legacy 方式 2
  1274. Logger.Info($"开始进行 Forge 安装,Legacy 方式 2:{InstallerAddress}")
  1275. '解压 Jar 文件
  1276. Dim JarAddress As String = McLibGet(Json("install")("path"), CustomMcFolder:=McFolder)
  1277. FileUtils.Write(JarAddress, Installer.GetEntry(Json("install")("filePath")).Open)
  1278. Task.Progress = 0.9
  1279. '建立 Json 文件
  1280. Json("versionInfo")("id") = NewInstanceName
  1281. If Json("versionInfo")("inheritsFrom") Is Nothing Then CType(Json("versionInfo"), JObject).Add("inheritsFrom", Inherit)
  1282. FileUtils.Write(VersionFolder & NewInstanceName & ".json", Json("versionInfo").ToString)
  1283. End If
  1284. Catch ex As Exception
  1285. Throw New Exception("非新版方式安装 Forge 失败", ex)
  1286. Finally
  1287. Try
  1288. '清理文件
  1289. If Installer IsNot Nothing Then Installer.Dispose()
  1290. FileUtils.Delete(InstallerAddress)
  1291. DirectoryUtils.Delete(InstallerAddress & "_unrar\")
  1292. Catch ex As Exception
  1293. Logger.Warn(ex, "非新版方式安装 Forge 清理文件时出错")
  1294. End Try
  1295. End Try
  1296. End Sub) With {.ProgressWeight = 1})
  1297. End If
  1298. Return Loaders
  1299. End Function
  1300. #End Region
  1301. #Region "Forge 下载菜单"
  1302. Public Sub ForgeDownloadListItemPreload(Stack As StackPanel, Entries As List(Of DlForgeVersionEntry), OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean)
  1303. '如果只有一个版本,则不特别列出
  1304. If Entries.IsSingle Then Return
  1305. '获取推荐版本与最新版本
  1306. Dim FreshVersion As DlForgeVersionEntry = Nothing
  1307. If Entries.Any Then
  1308. FreshVersion = Entries(0)
  1309. Else
  1310. Logger.Info("未找到可用的 Forge 版本")
  1311. End If
  1312. Dim RecommendedVersion As DlForgeVersionEntry = Nothing
  1313. For Each Entry In Entries
  1314. If Entry.IsRecommended Then RecommendedVersion = Entry
  1315. Next
  1316. '若推荐版本与最新版本为同一版本,则仅显示推荐版本
  1317. If FreshVersion IsNot Nothing AndAlso FreshVersion Is RecommendedVersion Then FreshVersion = Nothing
  1318. '显示各个版本
  1319. If RecommendedVersion IsNot Nothing Then
  1320. Dim Recommended = ForgeDownloadListItem(RecommendedVersion, OnClick, IsSaveOnly).Init()
  1321. Recommended.Info = "推荐版" & If(Recommended.Info = "", "", "," & Recommended.Info)
  1322. Stack.Children.Add(Recommended)
  1323. End If
  1324. If FreshVersion IsNot Nothing Then
  1325. Dim Fresh = ForgeDownloadListItem(FreshVersion, OnClick, IsSaveOnly).Init()
  1326. Fresh.Info = "最新版" & If(Fresh.Info = "", "", "," & Fresh.Info)
  1327. Stack.Children.Add(Fresh)
  1328. End If
  1329. '添加间隔
  1330. Stack.Children.Add(New TextBlock With {.Text = "全部版本 (" & Entries.Count & ")", .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 13, 0, 4)})
  1331. End Sub
  1332. Public Function ForgeDownloadListItem(Entry As DlForgeVersionEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyVirtualizingElement(Of MyListItem)
  1333. Return New MyVirtualizingElement(Of MyListItem)(
  1334. Function()
  1335. '建立控件
  1336. Dim NewItem As New MyListItem With {
  1337. .Title = Entry.VersionName, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
  1338. .Info = {If(Entry.ReleaseTime = "", "", "发布于 " & Entry.ReleaseTime), If(ModeDebug, "种类:" & Entry.Category, "")}.
  1339. Where(Function(d) d <> "").Join(","),
  1340. .Logo = PathImage & "Blocks/Anvil.png"
  1341. }
  1342. AddHandler NewItem.Click, OnClick
  1343. '建立菜单
  1344. If IsSaveOnly Then
  1345. NewItem.ContentHandler = AddressOf ForgeSaveContMenuBuild
  1346. Else
  1347. NewItem.ContentHandler = AddressOf ForgeContMenuBuild
  1348. End If
  1349. '结束
  1350. Return NewItem
  1351. End Function) With {.Height = 42}
  1352. End Function
  1353. Private Sub ForgeContMenuBuild(sender As MyListItem, e As EventArgs)
  1354. Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"}
  1355. AddHandler BtnSave.Click, AddressOf ForgeSave_Click
  1356. Dim BtnInfo As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .ToolTip = "更新日志"}
  1357. AddHandler BtnInfo.Click, AddressOf ForgeLog_Click
  1358. sender.Buttons = {BtnSave, BtnInfo}
  1359. End Sub
  1360. Private Sub ForgeSaveContMenuBuild(sender As MyListItem, e As EventArgs)
  1361. Dim BtnInfo As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .ToolTip = "更新日志"}
  1362. AddHandler BtnInfo.Click, AddressOf ForgeLog_Click
  1363. sender.Buttons = {BtnInfo}
  1364. End Sub
  1365. Private Sub ForgeLog_Click(sender As Object, e As RoutedEventArgs)
  1366. Dim Version As DlForgeVersionEntry
  1367. If sender.Tag IsNot Nothing Then
  1368. Version = sender.Tag
  1369. ElseIf sender.Parent.Tag IsNot Nothing Then
  1370. Version = sender.Parent.Tag
  1371. Else
  1372. Version = sender.Parent.Parent.Tag
  1373. End If
  1374. OpenWebsite($"https://files.minecraftforge.net/maven/net/minecraftforge/forge/{Version.Inherit}-{Version.VersionName}/forge-{Version.Inherit}-{Version.VersionName}-changelog.txt")
  1375. End Sub
  1376. Public Sub ForgeSave_Click(sender As Object, e As RoutedEventArgs)
  1377. Dim Version As DlForgeVersionEntry
  1378. If sender.Tag IsNot Nothing Then
  1379. Version = sender.Tag
  1380. ElseIf sender.Parent.Tag IsNot Nothing Then
  1381. Version = sender.Parent.Tag
  1382. Else
  1383. Version = sender.Parent.Parent.Tag
  1384. End If
  1385. McDownloadForgelikeSave(Version)
  1386. End Sub
  1387. #End Region
  1388. #Region "Forge 推荐版本获取"
  1389. ''' <summary>
  1390. ''' 尝试刷新 Forge 推荐版本缓存。
  1391. ''' </summary>
  1392. Public Sub McDownloadForgeRecommendedRefresh()
  1393. If IsForgeRecommendedRefreshed Then Return
  1394. IsForgeRecommendedRefreshed = True
  1395. RunInNewThread(
  1396. Sub()
  1397. Try
  1398. Logger.Info("刷新 Forge 推荐版本缓存开始")
  1399. Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/promos", RequireJson:=True)
  1400. If Result.Length < 1000 Then Throw New Exception("获取的结果过短(" & Result & ")")
  1401. Dim ResultJson As JContainer = Result.DeserializeJson()
  1402. '获取所有推荐版本列表
  1403. Dim RecommendedList As New List(Of String)
  1404. For Each Version As JObject In ResultJson
  1405. If Version("name") Is Nothing OrElse Version("build") Is Nothing Then Continue For
  1406. Dim Name As String = Version("name")
  1407. If Not Name.EndsWithF("-recommended") Then Continue For
  1408. '内容为:"1.15.2":"31.2.0"
  1409. RecommendedList.Add("""" & Name.Replace("-recommended", """:""" & Version("build")("version").ToString & """"))
  1410. Next
  1411. If RecommendedList.Count < 5 Then Throw New Exception("获取的推荐版本数过少(" & Result & ")")
  1412. '保存
  1413. Dim CacheJson As String = "{" & RecommendedList.Join(",") & "}"
  1414. FileUtils.Write(PathTemp & "Cache\ForgeRecommendedList.json", CacheJson)
  1415. Logger.Info("刷新 Forge 推荐版本缓存成功")
  1416. Catch ex As Exception
  1417. Logger.Warn(ex, "刷新 Forge 推荐版本缓存失败")
  1418. End Try
  1419. End Sub, "ForgeRecommendedRefresh")
  1420. End Sub
  1421. Private IsForgeRecommendedRefreshed As Boolean = False
  1422. ''' <summary>
  1423. ''' 尝试获取某个 MC 版本对应的 Forge 推荐版本。如果不可用会返回 Nothing。
  1424. ''' </summary>
  1425. Public Function McDownloadForgeRecommendedGet(McVersion As String) As String
  1426. Try
  1427. If McVersion Is Nothing Then Return Nothing
  1428. Dim List As String = FileUtils.TryReadAsString(PathTemp & "Cache\ForgeRecommendedList.json")
  1429. If String.IsNullOrEmpty(List) Then
  1430. Logger.Info("没有 Forge 推荐版本缓存文件")
  1431. Return Nothing
  1432. End If
  1433. Dim Json As JObject = List.DeserializeJson()
  1434. If Json Is Nothing OrElse (Not If(McVersion, "").Contains(".")) OrElse Not Json.ContainsKey(McVersion) Then Return Nothing
  1435. Return If(Json(McVersion), "").ToString
  1436. Catch ex As Exception
  1437. Logger.Error(ex, $"获取 Forge 推荐版本失败({If(McVersion, "null")})")
  1438. Return Nothing
  1439. End Try
  1440. End Function
  1441. #End Region
  1442. #Region "NeoForge 下载菜单"
  1443. Public Sub NeoForgeDownloadListItemPreload(Stack As StackPanel, Entries As List(Of DlNeoForgeListEntry), OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean)
  1444. '如果只有一个版本,则不特别列出
  1445. If Entries.IsSingle Then Return
  1446. '获取最新稳定版和测试版
  1447. Dim FreshStableVersion As DlNeoForgeListEntry = Nothing
  1448. Dim FreshBetaVersion As DlNeoForgeListEntry = Nothing
  1449. If Entries.Any() Then
  1450. For Each Entry In Entries.ToList()
  1451. If Entry.IsBeta Then
  1452. If FreshBetaVersion Is Nothing Then FreshBetaVersion = Entry
  1453. Else
  1454. FreshStableVersion = Entry
  1455. Exit For
  1456. End If
  1457. Next
  1458. Else
  1459. Logger.Warn("未找到可用的 NeoForge 版本")
  1460. End If
  1461. '显示各个版本
  1462. If FreshStableVersion IsNot Nothing Then
  1463. Dim Fresh = NeoForgeDownloadListItem(FreshStableVersion, OnClick, IsSaveOnly).Init()
  1464. Fresh.Info = If(Fresh.Info = "", "最新稳定版", "最新" & Fresh.Info)
  1465. Stack.Children.Add(Fresh)
  1466. End If
  1467. If FreshBetaVersion IsNot Nothing Then
  1468. Dim Fresh = NeoForgeDownloadListItem(FreshBetaVersion, OnClick, IsSaveOnly).Init()
  1469. Fresh.Info = If(Fresh.Info = "", "最新测试版", "最新" & Fresh.Info)
  1470. Stack.Children.Add(Fresh)
  1471. End If
  1472. '添加间隔
  1473. Stack.Children.Add(New TextBlock With {.Text = "全部版本 (" & Entries.Count & ")", .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 13, 0, 4)})
  1474. End Sub
  1475. Public Function NeoForgeDownloadListItem(Info As DlNeoForgeListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyVirtualizingElement(Of MyListItem)
  1476. Return New MyVirtualizingElement(Of MyListItem)(
  1477. Function()
  1478. '建立控件
  1479. Dim NewItem As New MyListItem With {
  1480. .Title = Info.VersionName, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Info,
  1481. .Info = If(Info.IsBeta, "测试版", "稳定版"),
  1482. .Logo = PathImage & "Blocks/NeoForge.png"
  1483. }
  1484. AddHandler NewItem.Click, OnClick
  1485. '建立菜单
  1486. If IsSaveOnly Then
  1487. NewItem.ContentHandler = AddressOf NeoForgeSaveContMenuBuild
  1488. Else
  1489. NewItem.ContentHandler = AddressOf NeoForgeContMenuBuild
  1490. End If
  1491. Return NewItem
  1492. End Function
  1493. ) With {.Height = 42}
  1494. End Function
  1495. Private Sub NeoForgeContMenuBuild(sender As MyListItem, e As EventArgs)
  1496. Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"}
  1497. AddHandler BtnSave.Click, AddressOf NeoForgeSave_Click
  1498. Dim BtnInfo As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .ToolTip = "更新日志"}
  1499. AddHandler BtnInfo.Click, AddressOf NeoForgeLog_Click
  1500. sender.Buttons = {BtnSave, BtnInfo}
  1501. End Sub
  1502. Private Sub NeoForgeSaveContMenuBuild(sender As MyListItem, e As EventArgs)
  1503. Dim BtnInfo As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .ToolTip = "更新日志"}
  1504. AddHandler BtnInfo.Click, AddressOf NeoForgeLog_Click
  1505. sender.Buttons = {BtnInfo}
  1506. End Sub
  1507. Private Sub NeoForgeLog_Click(sender As Object, e As RoutedEventArgs)
  1508. Dim Info As DlNeoForgeListEntry
  1509. If sender.Tag IsNot Nothing Then
  1510. Info = sender.Tag
  1511. ElseIf sender.Parent.Tag IsNot Nothing Then
  1512. Info = sender.Parent.Tag
  1513. Else
  1514. Info = sender.Parent.Parent.Tag
  1515. End If
  1516. OpenWebsite(Info.UrlBase & "-changelog.txt")
  1517. End Sub
  1518. Public Sub NeoForgeSave_Click(sender As Object, e As RoutedEventArgs)
  1519. Dim Info As DlNeoForgeListEntry
  1520. If sender.Tag IsNot Nothing Then
  1521. Info = sender.Tag
  1522. ElseIf sender.Parent.Tag IsNot Nothing Then
  1523. Info = sender.Parent.Tag
  1524. Else
  1525. Info = sender.Parent.Parent.Tag
  1526. End If
  1527. McDownloadForgelikeSave(Info)
  1528. End Sub
  1529. #End Region
  1530. #Region "Fabric 下载"
  1531. Public Sub McDownloadFabricLoaderSave(DownloadInfo As JObject)
  1532. Try
  1533. Dim Url As String = DownloadInfo("url").ToString
  1534. Dim FileName As String = PathUtils.GetLastPart(Url)
  1535. Dim Version As String = PathUtils.GetLastPart(DownloadInfo("version").ToString)
  1536. Dim Target As String = Dialogs.SaveFile("选择保存位置", FileName, filter:={("jar", "Fabric 安装器")})
  1537. If Target Is Nothing Then Return
  1538. '重复任务检查
  1539. For Each OngoingLoader In LoaderTaskbar.ToList()
  1540. If OngoingLoader.Name <> $"Fabric {Version} 安装器下载" Then Continue For
  1541. Hint("该版本正在下载中!", HintType.Red)
  1542. Return
  1543. Next
  1544. '构造步骤加载器
  1545. Dim Loaders As New List(Of LoaderBase)
  1546. '下载
  1547. 'BMCLAPI 不支持 Fabric Installer 下载
  1548. Dim Address As New List(Of String)
  1549. Address.Add(Url)
  1550. Loaders.Add(New LoaderDownload("下载主文件", New List(Of NetFile) From {New NetFile(Address.ToArray, Target, New FileChecker With {.MinSize = 1024 * 64})}) With {.ProgressWeight = 15})
  1551. '启动
  1552. Dim Loader As New LoaderCombo(Of JObject)("Fabric " & Version & " 安装器下载", Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
  1553. Loader.Start(DownloadInfo)
  1554. LoaderTaskbarAdd(Loader)
  1555. FrmMain.BtnExtraDownload.ShowRefresh()
  1556. FrmMain.BtnExtraDownload.Ribble()
  1557. Catch ex As Exception
  1558. Logger.Error(ex, "开始 Fabric 安装器下载失败")
  1559. End Try
  1560. End Sub
  1561. ''' <summary>
  1562. ''' 获取下载某个 Fabric 版本的加载器列表。
  1563. ''' 这不会补全所需的支持库文件,需要后续手动补全支持库。
  1564. ''' </summary>
  1565. Private Function McDownloadFabricLoader(FabricVersion As String, MinecraftName As String, McFolder As String) As List(Of LoaderBase)
  1566. '参数初始化
  1567. Dim IsCustomFolder As Boolean = McFolder <> McFolderSelected
  1568. Dim Id As String = "fabric-loader-" & FabricVersion & "-" & MinecraftName
  1569. Dim VersionFolder As String = McFolder & "versions\" & Id & "\"
  1570. Dim Loaders As New List(Of LoaderBase)
  1571. '下载 Json
  1572. MinecraftName = MinecraftName.Replace("∞", "infinite") '放在 ID 后面避免影响版本文件夹名称
  1573. Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("获取 Fabric 主文件下载地址",
  1574. Sub(Task As LoaderTask(Of String, List(Of NetFile)))
  1575. Task.Output = New List(Of NetFile) From {New NetFile({
  1576. "https://bmclapi2.bangbang93.com/fabric-meta/v2/versions/loader/" & MinecraftName & "/" & FabricVersion & "/profile/json",
  1577. "https://meta.fabricmc.net/v2/versions/loader/" & MinecraftName & "/" & FabricVersion & "/profile/json"
  1578. }, VersionFolder & Id & ".json", New FileChecker With {.IsJson = True})} '构造文件请求
  1579. End Sub) With {.ProgressWeight = 0.5})
  1580. Loaders.Add(New LoaderDownload("下载 Fabric 主文件", New List(Of NetFile)) With {.ProgressWeight = 2.5})
  1581. Return Loaders
  1582. End Function
  1583. #End Region
  1584. #Region "Fabric 下载菜单"
  1585. Public Function FabricDownloadListItem(Entry As JObject, OnClick As MyListItem.ClickEventHandler) As MyVirtualizingElement(Of MyListItem)
  1586. Return New MyVirtualizingElement(Of MyListItem)(
  1587. Function()
  1588. Dim NewItem As New MyListItem With {
  1589. .Title = Entry("version").ToString.Replace("+build", ""), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
  1590. .Info = If(Entry("stable").ToObject(Of Boolean), "稳定版", "测试版"),
  1591. .Logo = PathImage & "Blocks/Fabric.png"
  1592. }
  1593. AddHandler NewItem.Click, OnClick
  1594. Return NewItem
  1595. End Function) With {.Height = 42}
  1596. End Function
  1597. Public Function FabricApiDownloadListItem(Entry As ResourceVersion, OnClick As MyListItem.ClickEventHandler) As MyVirtualizingElement(Of MyListItem)
  1598. Return New MyVirtualizingElement(Of MyListItem)(
  1599. Function()
  1600. Dim NewItem As New MyListItem With {
  1601. .Title = Entry.Display.Split("]")(1).Replace("Fabric API ", "").Replace(" build ", ".").BeforeFirst("+").Trim, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
  1602. .Info = Entry.ReleaseTypeDisplay & ",发布于 " & Entry.ReleaseDate.ToString("yyyy'/'MM'/'dd HH':'mm"),
  1603. .Logo = PathImage & "Blocks/Fabric.png"
  1604. }
  1605. AddHandler NewItem.Click, OnClick
  1606. Return NewItem
  1607. End Function) With {.Height = 42}
  1608. End Function
  1609. Public Function OptiFabricDownloadListItem(Entry As ResourceVersion, OnClick As MyListItem.ClickEventHandler) As MyVirtualizingElement(Of MyListItem)
  1610. Return New MyVirtualizingElement(Of MyListItem)(
  1611. Function()
  1612. Dim NewItem As New MyListItem With {
  1613. .Title = Entry.Display.Lower.Replace("optifabric-", "").Replace(".jar", "").Trim.TrimStart("v"), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
  1614. .Info = Entry.ReleaseTypeDisplay & ",发布于 " & Entry.ReleaseDate.ToString("yyyy'/'MM'/'dd HH':'mm"),
  1615. .Logo = PathImage & "Blocks/OptiFabric.png"
  1616. }
  1617. AddHandler NewItem.Click, OnClick
  1618. Return NewItem
  1619. End Function) With {.Height = 42}
  1620. End Function
  1621. #End Region
  1622. #Region "合并安装"
  1623. ''' <summary>
  1624. ''' 安装请求。
  1625. ''' </summary>
  1626. Public Class McInstallRequest
  1627. ''' <summary>
  1628. ''' 必填。安装目标版本名称。
  1629. ''' </summary>
  1630. Public NewInstanceName As String
  1631. ''' <summary>
  1632. ''' 必填。安装目标文件夹。
  1633. ''' </summary>
  1634. Public VersionFolder As String
  1635. ''' <summary>
  1636. ''' 必填。欲下载的 Minecraft 的版本名。
  1637. ''' </summary>
  1638. Public MinecraftName As String = Nothing
  1639. ''' <summary>
  1640. ''' 可选。欲下载的 Minecraft Json 地址。
  1641. ''' </summary>
  1642. Public MinecraftJson As String = Nothing
  1643. '若要下载 OptiFine,则需要在下面两项中完成至少一项
  1644. ''' <summary>
  1645. ''' 欲下载的 OptiFine 版本名。例如 HD_U_F6_pre1。
  1646. ''' </summary>
  1647. Public OptiFineVersion As String = Nothing
  1648. ''' <summary>
  1649. ''' 欲下载的 OptiFine 详细信息。
  1650. ''' </summary>
  1651. Public OptiFineEntry As DlOptiFineListEntry = Nothing
  1652. '若要下载 Forge,则需要在下面两项中完成至少一项
  1653. ''' <summary>
  1654. ''' 欲下载的 Forge 版本名。接受例如 36.1.4 / 14.23.5.2859 / 1.19-41.1.0 的输入。
  1655. ''' </summary>
  1656. Public ForgeVersion As String = Nothing
  1657. ''' <summary>
  1658. ''' 欲下载的 Forge。
  1659. ''' </summary>
  1660. Public ForgeEntry As DlForgeVersionEntry = Nothing
  1661. '若要下载 NeoForge,则需要在下面两项中完成至少一项
  1662. ''' <summary>
  1663. ''' 欲下载的 NeoForge 版本名。
  1664. ''' </summary>
  1665. Public NeoForgeVersion As String = Nothing
  1666. ''' <summary>
  1667. ''' 欲下载的 NeoForge。
  1668. ''' </summary>
  1669. Public NeoForgeEntry As DlNeoForgeListEntry = Nothing
  1670. ''' <summary>
  1671. ''' 欲下载的 Fabric Loader 版本名。
  1672. ''' </summary>
  1673. Public FabricVersion As String = Nothing
  1674. ''' <summary>
  1675. ''' 欲下载的 Fabric API 信息。
  1676. ''' </summary>
  1677. Public FabricApi As ResourceVersion = Nothing
  1678. ''' <summary>
  1679. ''' 欲下载的 OptiFabric 信息。
  1680. ''' </summary>
  1681. Public OptiFabric As ResourceVersion = Nothing
  1682. ''' <summary>
  1683. ''' 欲下载的 LiteLoader 详细信息。
  1684. ''' </summary>
  1685. Public LiteLoaderEntry As DlLiteLoaderListEntry = Nothing
  1686. End Class
  1687. ''' <summary>
  1688. ''' 在加载器状态改变后显示一条提示。
  1689. ''' 不会进行任何其他操作。
  1690. ''' </summary>
  1691. Public Sub LoaderStateChangedHintOnly(Loader As LoaderBase)
  1692. Select Case Loader.State
  1693. Case LoadState.Finished
  1694. Hint(Loader.Name & "成功!", HintType.Green)
  1695. Case LoadState.Failed
  1696. Hint(Loader.Name & "失败:" & Loader.Error.GetDisplay(False), HintType.Red)
  1697. Case LoadState.Canceled
  1698. Hint(Loader.Name & "已取消!", HintType.Blue)
  1699. End Select
  1700. End Sub
  1701. ''' <summary>
  1702. ''' 安装加载器状态改变后进行提示和重载文件夹列表的方法。
  1703. ''' </summary>
  1704. Public Sub McInstallState(Loader As LoaderBase)
  1705. Select Case Loader.State
  1706. Case LoadState.Finished
  1707. WriteIni(McFolderSelected & "PCL.ini", "InstanceCache", "") '清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存)
  1708. Hint(Loader.Name & "成功!", HintType.Green)
  1709. 'TODO: 自动选择安装成功的版本
  1710. Case LoadState.Failed
  1711. MyMsgBox(Loader.Error.GetDisplay(True), Loader.Name & "失败", IsWarn:=True)
  1712. Case LoadState.Canceled
  1713. Hint(Loader.Name & "已取消!", HintType.Blue)
  1714. Case LoadState.Loading
  1715. Return '不重新加载版本列表
  1716. End Select
  1717. McInstallFailedClearFolder(Loader)
  1718. LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
  1719. End Sub
  1720. Public Sub McInstallFailedClearFolder(Loader)
  1721. Try
  1722. If Loader.State <> LoadState.Failed AndAlso Loader.State <> LoadState.Canceled Then Return
  1723. Thread.Sleep(1000) '防止存在尚未完全释放的文件,导致清理失败(例如整合包安装)
  1724. '删除版本文件夹
  1725. If DirectoryUtils.Exists(Loader.Input & "saves\") OrElse DirectoryUtils.Exists(Loader.Input & "versions\") Then
  1726. Logger.Warn($"由于版本已被独立启动,不清理版本文件夹:{Loader.Input}")
  1727. Else
  1728. Logger.Warn($"由于下载失败或取消,清理版本文件夹:{Loader.Input}")
  1729. DirectoryUtils.Delete(Loader.Input)
  1730. End If
  1731. Catch ex As Exception
  1732. Logger.Warn(ex, "下载失败或取消后清理版本文件夹失败")
  1733. End Try
  1734. End Sub
  1735. ''' <summary>
  1736. ''' 进行合并安装。返回是否已经开始安装(例如如果没有安装 Java 则会进行提示并返回 False)
  1737. ''' </summary>
  1738. Public Function McInstall(Request As McInstallRequest) As Boolean
  1739. Try
  1740. Dim SubLoaders = McInstallLoader(Request)
  1741. If SubLoaders Is Nothing Then Return False
  1742. Dim Loader As New LoaderCombo(Of String)(Request.NewInstanceName & " 安装", SubLoaders) With {.OnStateChanged = AddressOf McInstallState}
  1743. '启动
  1744. Loader.Start(Request.VersionFolder)
  1745. LoaderTaskbarAdd(Loader)
  1746. FrmMain.BtnExtraDownload.ShowRefresh()
  1747. FrmMain.BtnExtraDownload.Ribble()
  1748. Return True
  1749. Catch ex As Exception
  1750. Logger.Error(ex, "开始合并安装失败")
  1751. Return False
  1752. End Try
  1753. End Function
  1754. ''' <summary>
  1755. ''' 获取合并安装加载器列表,并进行前期的缓存清理与 Java 检查工作。
  1756. ''' </summary>
  1757. Public Function McInstallLoader(Request As McInstallRequest) As List(Of LoaderBase)
  1758. '获取缓存目录(安装 Mod 加载器的文件夹不能包含空格)
  1759. Dim TempMcFolder As String = RequestTaskTempFolder(Request.OptiFineEntry IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing)
  1760. '获取参数
  1761. Dim VersionFolder As String = McFolderSelected & "versions\" & Request.NewInstanceName & "\"
  1762. If DirectoryUtils.Exists(TempMcFolder) Then DirectoryUtils.Delete(TempMcFolder)
  1763. Dim OptiFineFolder As String = Nothing
  1764. If Request.OptiFineVersion IsNot Nothing Then
  1765. If Request.OptiFineVersion.Contains("_HD_U_") Then Request.OptiFineVersion = "HD_U_" & Request.OptiFineVersion.AfterLast("_HD_U_") '#735
  1766. Request.OptiFineEntry = New DlOptiFineListEntry With {
  1767. .DisplayName = Request.MinecraftName & " " & Request.OptiFineVersion.Replace("HD_U_", "").Replace("_", "").Replace("pre", " pre"),
  1768. .Inherit = Request.MinecraftName,
  1769. .IsPreview = Request.OptiFineVersion.ContainsIgnoreCase("pre"),
  1770. .InstanceName = Request.MinecraftName & "-OptiFine_" & Request.OptiFineVersion,
  1771. .FileName = If(Request.OptiFineVersion.ContainsIgnoreCase("pre"), "preview_", "") &
  1772. "OptiFine_" & Request.MinecraftName & "_" & Request.OptiFineVersion & ".jar"
  1773. }
  1774. End If
  1775. If Request.OptiFineEntry IsNot Nothing Then OptiFineFolder = TempMcFolder & "versions\" & Request.OptiFineEntry.InstanceName
  1776. Dim ForgeFolder As String = Nothing
  1777. If Request.ForgeEntry IsNot Nothing Then Request.ForgeVersion = If(Request.ForgeVersion, Request.ForgeEntry.VersionName)
  1778. If Request.ForgeVersion IsNot Nothing Then ForgeFolder = TempMcFolder & "versions\forge-" & Request.ForgeVersion
  1779. Dim NeoForgeFolder As String = Nothing
  1780. If Request.NeoForgeEntry IsNot Nothing Then Request.NeoForgeVersion = If(Request.NeoForgeVersion, Request.NeoForgeEntry.VersionName)
  1781. If Request.NeoForgeVersion IsNot Nothing Then NeoForgeFolder = TempMcFolder & "versions\neoforge-" & Request.NeoForgeVersion
  1782. Dim FabricFolder As String = Nothing
  1783. If Request.FabricVersion IsNot Nothing Then FabricFolder = TempMcFolder & "versions\fabric-loader-" & Request.FabricVersion & "-" & Request.MinecraftName
  1784. Dim LiteLoaderFolder As String = Nothing
  1785. If Request.LiteLoaderEntry IsNot Nothing Then LiteLoaderFolder = TempMcFolder & "versions\" & Request.MinecraftName & "-LiteLoader"
  1786. '判断 OptiFine 是否作为 Mod 进行下载
  1787. Dim Modable As Boolean = Request.FabricVersion IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing
  1788. Dim ModsTempFolder As String = TempMcFolder & "mods\"
  1789. Dim OptiFineAsMod As Boolean = Request.OptiFineEntry IsNot Nothing AndAlso Modable '选择了 OptiFine 与任意 Mod 加载器
  1790. If OptiFineAsMod Then
  1791. Logger.Info("OptiFine 将作为 Mod 进行下载")
  1792. If Request.LiteLoaderEntry IsNot Nothing Then
  1793. OptiFineFolder = ModsTempFolder & Request.MinecraftName & "\" '#8147
  1794. Else
  1795. OptiFineFolder = ModsTempFolder
  1796. End If
  1797. End If
  1798. '记录日志
  1799. If OptiFineFolder IsNot Nothing Then Logger.Info($"OptiFine 缓存:{OptiFineFolder}")
  1800. If ForgeFolder IsNot Nothing Then Logger.Info($"Forge 缓存:{ForgeFolder}")
  1801. If NeoForgeFolder IsNot Nothing Then Logger.Info($"NeoForge 缓存:{NeoForgeFolder}")
  1802. If FabricFolder IsNot Nothing Then Logger.Info($"Fabric 缓存:{FabricFolder}")
  1803. If LiteLoaderFolder IsNot Nothing Then Logger.Info($"LiteLoader 缓存:{LiteLoaderFolder}")
  1804. Logger.Info($"对应的原版版本:{Request.MinecraftName}")
  1805. '重复版本检查
  1806. If FileUtils.Exists($"{VersionFolder}{Request.NewInstanceName}.json") Then
  1807. Hint("版本 " & Request.NewInstanceName & " 已经存在!", HintType.Red)
  1808. Throw New OperationCanceledException
  1809. End If
  1810. Dim LoaderList As New List(Of LoaderBase)
  1811. '添加忽略标识
  1812. LoaderList.Add(New LoaderTask(Of Integer, Integer)("添加忽略标识", Sub() FileUtils.Write(VersionFolder & ".pclignore", "用于临时地在 PCL 的版本列表中屏蔽此版本。")) With {.Show = False, .Block = False})
  1813. 'Fabric API
  1814. If Request.FabricApi IsNot Nothing Then
  1815. LoaderList.Add(New LoaderDownload("下载 Fabric API", New List(Of NetFile) From {Request.FabricApi.ToNetFile(ModsTempFolder, ResourceVersion.DownloadReason.Dependency, Request.MinecraftName, ModLoaders.Fabric)}) With {.ProgressWeight = 3, .Block = False})
  1816. End If
  1817. 'OptiFabric
  1818. If Request.OptiFabric IsNot Nothing Then
  1819. LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {Request.OptiFabric.ToNetFile(ModsTempFolder, ResourceVersion.DownloadReason.Dependency, Request.MinecraftName, ModLoaders.Fabric)}) With {.ProgressWeight = 3, .Block = False})
  1820. End If
  1821. '原版
  1822. Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.MinecraftName, McDownloadClientLoader(Request.MinecraftName, Request.MinecraftJson, Request.NewInstanceName)) With {.Show = False, .ProgressWeight = 39,
  1823. .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.OptiFineEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}
  1824. LoaderList.Add(ClientLoader)
  1825. 'OptiFine
  1826. If Request.OptiFineEntry IsNot Nothing Then
  1827. If OptiFineAsMod Then
  1828. LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.DisplayName, McDownloadOptiFineSaveLoader(Request.OptiFineEntry, OptiFineFolder & Request.OptiFineEntry.FileName)) With {.Show = False, .ProgressWeight = 16,
  1829. .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
  1830. Else
  1831. LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.DisplayName, McDownloadOptiFineLoader(Request.OptiFineEntry, TempMcFolder, ClientLoader, Request.VersionFolder)) With {.Show = False, .ProgressWeight = 24,
  1832. .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
  1833. End If
  1834. End If
  1835. 'Forge
  1836. If Request.ForgeVersion IsNot Nothing Then
  1837. LoaderList.Add(New LoaderCombo(Of String)("下载 Forge " & Request.ForgeVersion, McDownloadForgelikeLoader(False, Request.ForgeVersion, "forge-" & Request.ForgeVersion, Request.MinecraftName, Request.ForgeEntry, TempMcFolder, ClientLoader, Request.VersionFolder)) With {.Show = False, .ProgressWeight = 25,
  1838. .Block = Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing AndAlso Request.NeoForgeEntry Is Nothing})
  1839. End If
  1840. 'NeoForge
  1841. If Request.NeoForgeVersion IsNot Nothing Then
  1842. LoaderList.Add(New LoaderCombo(Of String)("下载 NeoForge " & Request.NeoForgeVersion, McDownloadForgelikeLoader(True, Request.NeoForgeVersion, "neoforge-" & Request.NeoForgeVersion, Request.MinecraftName, Request.NeoForgeEntry, TempMcFolder, ClientLoader, Request.VersionFolder)) With {.Show = False, .ProgressWeight = 25,
  1843. .Block = Request.ForgeEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
  1844. End If
  1845. 'LiteLoader
  1846. If Request.LiteLoaderEntry IsNot Nothing Then
  1847. LoaderList.Add(New LoaderCombo(Of String)("下载 LiteLoader " & Request.MinecraftName, McDownloadLiteLoaderLoader(Request.LiteLoaderEntry, TempMcFolder)) With {.Show = False, .ProgressWeight = 1,
  1848. .Block = Request.FabricVersion Is Nothing})
  1849. End If
  1850. 'Fabric
  1851. If Request.FabricVersion IsNot Nothing Then
  1852. LoaderList.Add(New LoaderCombo(Of String)("下载 Fabric " & Request.FabricVersion, McDownloadFabricLoader(Request.FabricVersion, Request.MinecraftName, TempMcFolder)) With {.Show = False, .ProgressWeight = 2,
  1853. .Block = True})
  1854. End If
  1855. '合并安装
  1856. LoaderList.Add(New LoaderTask(Of String, String)("安装游戏",
  1857. Sub(Task As LoaderTask(Of String, String))
  1858. '合并 JSON
  1859. MergeJson(VersionFolder, VersionFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, NeoForgeFolder, Request.NeoForgeVersion, FabricFolder, LiteLoaderFolder)
  1860. Task.Progress = 0.2
  1861. '迁移文件
  1862. DirectoryUtils.Copy(TempMcFolder & "libraries", McFolderSelected & "libraries")
  1863. Task.Progress = 0.8
  1864. '创建 Mod 和资源包文件夹
  1865. Dim ModsFolder = New McInstance(VersionFolder).PathIndie & "mods\" '版本隔离信息在此时被决定
  1866. If DirectoryUtils.Exists(ModsTempFolder) Then
  1867. DirectoryUtils.Copy(ModsTempFolder, ModsFolder)
  1868. ElseIf Modable Then
  1869. DirectoryUtils.Create(ModsFolder)
  1870. Logger.Info($"自动创建 Mod 文件夹:{ModsFolder}")
  1871. End If
  1872. Dim ResourcepacksFolder = New McInstance(VersionFolder).PathIndie & "resourcepacks\"
  1873. DirectoryUtils.Create(ResourcepacksFolder)
  1874. Logger.Info($"自动创建资源包文件夹:{ResourcepacksFolder}")
  1875. End Sub) With {.ProgressWeight = 2, .Block = True})
  1876. '补全文件
  1877. If Request.OptiFineEntry IsNot Nothing OrElse (Request.ForgeVersion IsNot Nothing AndAlso Request.ForgeVersion.BeforeFirst(".") >= 20) OrElse Request.NeoForgeVersion IsNot Nothing OrElse Request.FabricVersion IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing Then
  1878. Dim LoadersLib As New List(Of LoaderBase)
  1879. LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task) Task.Output = McLibNetFilesFromInstance(New McInstance(VersionFolder))) With {.ProgressWeight = 1, .Show = False})
  1880. LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
  1881. LoaderList.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8})
  1882. End If
  1883. '删除忽略标识
  1884. LoaderList.Add(New LoaderTask(Of Integer, Integer)("删除忽略标识", Sub() FileUtils.Delete(VersionFolder & ".pclignore")) With {.Show = False})
  1885. '总加载器
  1886. Return LoaderList
  1887. End Function
  1888. ''' <summary>
  1889. ''' 将多个版本 JSON 进行合并,如果目标已存在则直接覆盖。失败会抛出异常。
  1890. ''' </summary>
  1891. Private Sub MergeJson(OutputFolder As String, MinecraftFolder As String, Optional OptiFineFolder As String = Nothing, Optional OptiFineAsMod As Boolean = False, Optional ForgeFolder As String = Nothing, Optional ForgeVersion As String = Nothing, Optional NeoForgeFolder As String = Nothing, Optional NeoForgeVersion As String = Nothing, Optional FabricFolder As String = Nothing, Optional LiteLoaderFolder As String = Nothing)
  1892. Logger.Info($"开始进行版本合并,输出:{OutputFolder},Minecraft:{MinecraftFolder}" &
  1893. If(OptiFineFolder IsNot Nothing, ",OptiFine:" & OptiFineFolder, "") &
  1894. If(ForgeFolder IsNot Nothing, ",Forge:" & ForgeFolder, "") &
  1895. If(NeoForgeFolder IsNot Nothing, ",NeoForge:" & NeoForgeFolder, "") &
  1896. If(LiteLoaderFolder IsNot Nothing, ",LiteLoader:" & LiteLoaderFolder, "") &
  1897. If(FabricFolder IsNot Nothing, ",Fabric:" & FabricFolder, ""))
  1898. DirectoryUtils.Create(OutputFolder)
  1899. Dim HasOptiFine As Boolean = OptiFineFolder IsNot Nothing AndAlso Not OptiFineAsMod, HasForge As Boolean = ForgeFolder IsNot Nothing, HasNeoForge As Boolean = NeoForgeFolder IsNot Nothing, HasLiteLoader As Boolean = LiteLoaderFolder IsNot Nothing, HasFabric As Boolean = FabricFolder IsNot Nothing
  1900. Dim OutputName As String, MinecraftName As String, OptiFineName As String, ForgeName As String, NeoForgeName As String, LiteLoaderName As String, FabricName As String
  1901. Dim OutputJsonPath As String, MinecraftJsonPath As String, OptiFineJsonPath As String = Nothing, ForgeJsonPath As String = Nothing, NeoForgeJsonPath As String = Nothing, LiteLoaderJsonPath As String = Nothing, FabricJsonPath As String = Nothing
  1902. Dim OutputJar As String, MinecraftJar As String
  1903. #Region "初始化路径信息"
  1904. If Not OutputFolder.EndsWithF("\") Then OutputFolder += "\"
  1905. OutputName = PathUtils.GetLastPart(OutputFolder)
  1906. OutputJsonPath = OutputFolder & OutputName & ".json"
  1907. OutputJar = OutputFolder & OutputName & ".jar"
  1908. If Not MinecraftFolder.EndsWithF("\") Then MinecraftFolder += "\"
  1909. MinecraftName = PathUtils.GetLastPart(MinecraftFolder)
  1910. MinecraftJsonPath = MinecraftFolder & MinecraftName & ".json"
  1911. MinecraftJar = MinecraftFolder & MinecraftName & ".jar"
  1912. If HasOptiFine Then
  1913. If Not OptiFineFolder.EndsWithF("\") Then OptiFineFolder += "\"
  1914. OptiFineName = PathUtils.GetLastPart(OptiFineFolder)
  1915. OptiFineJsonPath = OptiFineFolder & OptiFineName & ".json"
  1916. End If
  1917. If HasForge Then
  1918. If Not ForgeFolder.EndsWithF("\") Then ForgeFolder += "\"
  1919. ForgeName = PathUtils.GetLastPart(ForgeFolder)
  1920. ForgeJsonPath = ForgeFolder & ForgeName & ".json"
  1921. End If
  1922. If HasNeoForge Then
  1923. If Not NeoForgeFolder.EndsWithF("\") Then NeoForgeFolder += "\"
  1924. NeoForgeName = PathUtils.GetLastPart(NeoForgeFolder)
  1925. NeoForgeJsonPath = NeoForgeFolder & NeoForgeName & ".json"
  1926. End If
  1927. If HasLiteLoader Then
  1928. If Not LiteLoaderFolder.EndsWithF("\") Then LiteLoaderFolder += "\"
  1929. LiteLoaderName = PathUtils.GetLastPart(LiteLoaderFolder)
  1930. LiteLoaderJsonPath = LiteLoaderFolder & LiteLoaderName & ".json"
  1931. End If
  1932. If HasFabric Then
  1933. If Not FabricFolder.EndsWithF("\") Then FabricFolder += "\"
  1934. FabricName = PathUtils.GetLastPart(FabricFolder)
  1935. FabricJsonPath = FabricFolder & FabricName & ".json"
  1936. End If
  1937. #End Region
  1938. Dim OutputJson As JObject, MinecraftJson As JObject, OptiFineJson As JObject = Nothing, ForgeJson As JObject = Nothing, NeoForgeJson As JObject = Nothing, LiteLoaderJson As JObject = Nothing, FabricJson As JObject = Nothing
  1939. #Region "读取文件并检查文件是否合规"
  1940. Dim MinecraftJsonText As String = If(FileUtils.TryReadAsString(MinecraftJsonPath), "")
  1941. If Not MinecraftJsonText.StartsWithF("{") Then Throw New Exception("Minecraft json 有误,地址:" & MinecraftJsonPath & ",前段内容:" & MinecraftJsonText.Substring(0, Math.Min(MinecraftJsonText.Length, 1000)))
  1942. MinecraftJson = MinecraftJsonText.DeserializeJson()
  1943. If HasOptiFine Then
  1944. Dim OptiFineJsonText As String = If(FileUtils.TryReadAsString(OptiFineJsonPath), "")
  1945. If Not OptiFineJsonText.StartsWithF("{") Then Throw New Exception("OptiFine json 有误,地址:" & OptiFineJsonPath & ",前段内容:" & OptiFineJsonText.Substring(0, Math.Min(OptiFineJsonText.Length, 1000)))
  1946. OptiFineJson = OptiFineJsonText.DeserializeJson()
  1947. End If
  1948. If HasForge Then
  1949. Dim ForgeJsonText As String = If(FileUtils.TryReadAsString(ForgeJsonPath), "")
  1950. If Not ForgeJsonText.StartsWithF("{") Then Throw New Exception("Forge json 有误,地址:" & ForgeJsonPath & ",前段内容:" & ForgeJsonText.Substring(0, Math.Min(ForgeJsonText.Length, 1000)))
  1951. ForgeJson = ForgeJsonText.DeserializeJson()
  1952. End If
  1953. If HasNeoForge Then
  1954. Dim NeoForgeJsonText As String = If(FileUtils.TryReadAsString(NeoForgeJsonPath), "")
  1955. If Not NeoForgeJsonText.StartsWithF("{") Then Throw New Exception("NeoForge json 有误,地址:" & NeoForgeJsonPath & ",前段内容:" & NeoForgeJsonText.Substring(0, Math.Min(NeoForgeJsonText.Length, 1000)))
  1956. NeoForgeJson = NeoForgeJsonText.DeserializeJson()
  1957. End If
  1958. If HasLiteLoader Then
  1959. Dim LiteLoaderJsonText As String = If(FileUtils.TryReadAsString(LiteLoaderJsonPath), "")
  1960. If Not LiteLoaderJsonText.StartsWithF("{") Then Throw New Exception("LiteLoader json 有误,地址:" & LiteLoaderJsonPath & ",前段内容:" & LiteLoaderJsonText.Substring(0, Math.Min(LiteLoaderJsonText.Length, 1000)))
  1961. LiteLoaderJson = LiteLoaderJsonText.DeserializeJson()
  1962. End If
  1963. If HasFabric Then
  1964. Dim FabricJsonText As String = If(FileUtils.TryReadAsString(FabricJsonPath), "")
  1965. If Not FabricJsonText.StartsWithF("{") Then Throw New Exception("Fabric json 有误,地址:" & FabricJsonPath & ",前段内容:" & FabricJsonText.Substring(0, Math.Min(FabricJsonText.Length, 1000)))
  1966. FabricJson = FabricJsonText.DeserializeJson()
  1967. End If
  1968. #End Region
  1969. #Region "处理 JSON 文件"
  1970. '获取 minecraftArguments
  1971. Dim AllArguments As String =
  1972. If(MinecraftJson("minecraftArguments"), " ").ToString & " " &
  1973. If(OptiFineJson IsNot Nothing, If(OptiFineJson("minecraftArguments"), " ").ToString, " ") & " " &
  1974. If(ForgeJson IsNot Nothing, If(ForgeJson("minecraftArguments"), " ").ToString, " ") & " " &
  1975. If(NeoForgeJson IsNot Nothing, If(NeoForgeJson("minecraftArguments"), " ").ToString, " ") & " " &
  1976. If(LiteLoaderJson IsNot Nothing, If(LiteLoaderJson("minecraftArguments"), " ").ToString, " ")
  1977. '分割参数字符串
  1978. Dim RawArguments As List(Of String) = AllArguments.Split(" ", True).Select(Function(l) l.Trim).ToList
  1979. Dim SplitArguments As New List(Of String)
  1980. For i = 0 To RawArguments.Count - 1
  1981. If RawArguments(i).StartsWithF("-") Then
  1982. SplitArguments.Add(RawArguments(i))
  1983. ElseIf SplitArguments.Any AndAlso SplitArguments.Last.StartsWithF("-") AndAlso Not SplitArguments.Last.Contains(" ") Then
  1984. SplitArguments(SplitArguments.Count - 1) = SplitArguments.Last & " " & RawArguments(i)
  1985. Else
  1986. SplitArguments.Add(RawArguments(i))
  1987. End If
  1988. Next
  1989. Dim RealArguments As String = SplitArguments.Distinct.Join(" ")
  1990. '合并
  1991. '相关讨论见 #2801
  1992. OutputJson = MinecraftJson
  1993. If HasOptiFine Then
  1994. '合并 OptiFine
  1995. OptiFineJson.Remove("releaseTime")
  1996. OptiFineJson.Remove("time")
  1997. OutputJson.Merge(OptiFineJson)
  1998. End If
  1999. If HasForge Then
  2000. '合并 Forge
  2001. ForgeJson.Remove("releaseTime")
  2002. ForgeJson.Remove("time")
  2003. OutputJson.Merge(ForgeJson)
  2004. End If
  2005. If HasNeoForge Then
  2006. '合并 NeoForge
  2007. NeoForgeJson.Remove("releaseTime")
  2008. NeoForgeJson.Remove("time")
  2009. OutputJson.Merge(NeoForgeJson)
  2010. End If
  2011. If HasLiteLoader Then
  2012. '合并 LiteLoader
  2013. LiteLoaderJson.Remove("releaseTime")
  2014. LiteLoaderJson.Remove("time")
  2015. OutputJson.Merge(LiteLoaderJson)
  2016. End If
  2017. If HasFabric Then
  2018. '合并 Fabric
  2019. FabricJson.Remove("releaseTime")
  2020. FabricJson.Remove("time")
  2021. OutputJson.Merge(FabricJson)
  2022. End If
  2023. '修改
  2024. If RealArguments IsNot Nothing AndAlso RealArguments.Replace(" ", "") <> "" Then OutputJson("minecraftArguments") = RealArguments
  2025. OutputJson.Remove("_comment_")
  2026. OutputJson.Remove("inheritsFrom")
  2027. OutputJson.Remove("jar")
  2028. OutputJson("id") = OutputName
  2029. #End Region
  2030. #Region "保存"
  2031. FileUtils.Write(OutputJsonPath, OutputJson.ToString)
  2032. FileUtils.Copy(MinecraftJar, OutputJar)
  2033. Logger.Info($"版本合并 {OutputName} 完成")
  2034. #End Region
  2035. End Sub
  2036. #End Region
  2037. ''' <summary>
  2038. ''' 如果 OptiFine 与 Forge 同时开始安装,就会导致 Forge 安装失败。
  2039. ''' </summary>
  2040. Private InstallSyncLock As New Object
  2041. ''' <summary>
  2042. ''' 如果 OptiFine 与 Forge 同时复制原版 JAR,就会导致复制文件时冲突。
  2043. ''' </summary>
  2044. Private VanillaSyncLock As New Object
  2045. End Module