ModDownloadLib.vb 120 KB

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