ModDownloadLib.vb 124 KB

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