1
0

ModDownloadLib.vb 123 KB

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