ModModpack.vb 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. Imports System.Collections.ObjectModel
  2. Imports System.Text.RegularExpressions
  3. Public Module ModModpack
  4. '触发整合包安装的外部接口
  5. ''' <summary>
  6. ''' 弹窗要求选择一个整合包文件并进行安装。
  7. ''' </summary>
  8. Public Sub ModpackInstall()
  9. If RestrictMcDownload Then
  10. Hint("为遵守相关法律法规,PCL 不再提供 MC 下载功能,非常抱歉 qwq……", HintType.Red)
  11. Return
  12. End If
  13. Dim File As String = Dialogs.SelectFile("选择整合包压缩文件", False, filter:={({"rar", "zip", "mrpack"}, "整合包文件")}).FirstOrDefault() '选择整合包文件
  14. If String.IsNullOrEmpty(File) Then Return
  15. RunInThread(
  16. Sub()
  17. Try
  18. ModpackInstall(File)
  19. Catch ex As Exception
  20. ex.ThrowIfCanceled()
  21. Logger.Error(ex, "手动安装整合包失败", LogBehavior.Alert)
  22. End Try
  23. End Sub)
  24. End Sub
  25. ''' <summary>
  26. ''' 构建并启动安装给定的整合包文件的加载器,并返回该加载器。若失败则抛出异常。
  27. ''' 必须在工作线程执行。
  28. ''' </summary>
  29. Public Function ModpackInstall(File As String, Optional InstanceName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String)
  30. Logger.Info($"整合包安装请求:{If(File, "null")}")
  31. Dim Archive As ZipArchive = Nothing
  32. Dim ArchiveBaseFolder As String = ""
  33. Try
  34. '字符校验
  35. Dim TargetFolder As String = $"{McFolderSelected}versions\{InstanceName}\"
  36. If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("游戏路径中不能含有感叹号或分号:" & TargetFolder, HintType.Red) : Throw New OperationCanceledException
  37. '获取整合包种类与关键 Json
  38. Dim PackType As Integer = -1
  39. Try
  40. Archive = FileUtils.OpenZip(File)
  41. '从根目录判断整合包类型
  42. If Archive.GetEntry("mcbbs.packmeta") IsNot Nothing Then PackType = 3 : Exit Try 'MCBBS 整合包(优先于 manifest.json 判断)
  43. If Archive.GetEntry("mmc-pack.json") IsNot Nothing Then PackType = 2 : Exit Try 'MMC 整合包(优先于 manifest.json 判断,#4194)
  44. If Archive.GetEntry("modrinth.index.json") IsNot Nothing Then PackType = 4 : Exit Try 'Modrinth 整合包
  45. If Archive.GetEntry("manifest.json") IsNot Nothing Then
  46. Dim Json As JObject = Archive.GetEntry("manifest.json").Open().ReadString(Encoding.UTF8).DeserializeJson()
  47. If Json("addons") Is Nothing Then
  48. PackType = 0 : Exit Try 'CurseForge 整合包
  49. Else
  50. PackType = 3 : Exit Try 'MCBBS 整合包
  51. End If
  52. End If
  53. If Archive.GetEntry("modpack.json") IsNot Nothing Then PackType = 1 : Exit Try 'HMCL 整合包
  54. If Archive.GetEntry("modpack.zip") IsNot Nothing OrElse Archive.GetEntry("modpack.mrpack") IsNot Nothing Then PackType = 9 : Exit Try '带启动器的压缩包
  55. '从一级目录判断整合包类型
  56. For Each Entry In Archive.Entries
  57. Dim PathSplits As String() = Entry.FullName.Split("/")
  58. ArchiveBaseFolder = PathSplits(0) & "/"
  59. '确定为一级目录下
  60. If PathSplits.Count <> 2 Then Continue For
  61. '判断是否为关键文件
  62. If PathSplits(1) = "mcbbs.packmeta" Then PackType = 3 : Exit Try 'MCBBS 整合包(优先于 manifest.json 判断)
  63. If PathSplits(1) = "mmc-pack.json" Then PackType = 2 : Exit Try 'MMC 整合包(优先于 manifest.json 判断,#4194)
  64. If PathSplits(1) = "modrinth.index.json" Then PackType = 4 : Exit Try 'Modrinth 整合包
  65. If PathSplits(1) = "manifest.json" Then
  66. Dim Json As JObject = Entry.Open().ReadString(Encoding.UTF8).DeserializeJson()
  67. If Json("addons") Is Nothing Then
  68. PackType = 0 : Exit Try 'CurseForge 整合包
  69. Else
  70. PackType = 3 : ArchiveBaseFolder = "overrides/" : Exit Try 'MCBBS 整合包
  71. End If
  72. End If
  73. If PathSplits(1) = "modpack.json" Then PackType = 1 : Exit Try 'HMCL 整合包
  74. If PathSplits(1) = "modpack.zip" OrElse PathSplits(1) = "modpack.mrpack" Then PackType = 9 : Exit Try '带启动器的压缩包
  75. Next
  76. Catch ex As Exception
  77. If ex.GetDisplay(True).Contains("Error.WinIOError") Then
  78. Throw New Exception("打开整合包文件失败", ex)
  79. ElseIf PathUtils.GetExtension(File) = "rar" Then
  80. Throw New Exception("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试", ex)
  81. Else
  82. Throw New Exception("打开整合包文件失败,文件可能损坏或为不支持的压缩包格式", ex)
  83. End If
  84. End Try
  85. '执行对应的安装方法
  86. If RestrictMcDownload Then Throw New Exception("为遵守相关法律法规,PCL 不再提供 MC 下载功能,非常抱歉 qwq……")
  87. Select Case PackType
  88. Case 0
  89. Logger.Info("整合包种类:CurseForge")
  90. Return InstallPackCurseForge(File, Archive, ArchiveBaseFolder, InstanceName, Logo)
  91. Case 1
  92. Logger.Info("整合包种类:HMCL")
  93. Return InstallPackHMCL(File, Archive, ArchiveBaseFolder)
  94. Case 2
  95. Logger.Info("整合包种类:MMC")
  96. Return InstallPackMMC(File, Archive, ArchiveBaseFolder)
  97. Case 3
  98. Logger.Info("整合包种类:MCBBS")
  99. Return InstallPackMCBBS(File, Archive, ArchiveBaseFolder, InstanceName)
  100. Case 4
  101. Logger.Info("整合包种类:Modrinth")
  102. Return InstallPackModrinth(File, Archive, ArchiveBaseFolder, InstanceName, Logo)
  103. Case 9
  104. Logger.Info("整合包种类:带启动器的压缩包")
  105. Return InstallPackLauncherPack(File, Archive, ArchiveBaseFolder)
  106. Case Else
  107. Logger.Info("整合包种类:未能识别,假定为压缩包")
  108. Return InstallPackCompress(File, Archive)
  109. End Select
  110. Finally
  111. If Archive IsNot Nothing Then Archive.Dispose()
  112. End Try
  113. End Function
  114. Private Sub ExtractModpackFiles(InstallTemp As String, FileAddress As String, Optional c As CancellationToken = Nothing, Optional p As ProgressProvider = Nothing)
  115. DirectoryUtils.Delete(InstallTemp)
  116. c.ThrowIfCancellationRequested()
  117. FileUtils.ExtractToDirectory(FileAddress, InstallTemp, c, p)
  118. End Sub
  119. ''' <summary>
  120. ''' 从整合包的 override 目录复制文件,同时设置 PCL 的配置文件与版本隔离。
  121. ''' 对路径末尾是否为 \ 没有要求。
  122. ''' </summary>
  123. Private Sub CopyOverrideDirectory(OverridesFolder As String, VersionFolder As String, Loader As LoaderBase)
  124. OverridesFolder = PathUtils.AddSlashSuffix(OverridesFolder)
  125. VersionFolder = PathUtils.AddSlashSuffix(VersionFolder)
  126. '复制文件
  127. If DirectoryUtils.Exists(OverridesFolder) Then
  128. Logger.Info($"处理整合包覆写文件夹:{OverridesFolder} → {VersionFolder}")
  129. DirectoryUtils.Copy(OverridesFolder, VersionFolder)
  130. Else
  131. Logger.Info($"整合包中没有覆写文件夹:{OverridesFolder}")
  132. End If
  133. '设置 ini
  134. Dim OverridesIni As String = $"{OverridesFolder}PCL\Setup.ini"
  135. Dim VersionIni As String = $"{VersionFolder}PCL\Setup.ini"
  136. If FileUtils.Exists(OverridesIni) Then
  137. WriteIni(OverridesIni, "VersionArgumentIndie", 1) '开启版本隔离
  138. WriteIni(OverridesIni, "VersionArgumentIndieV2", True)
  139. WriteIni(OverridesIni, "IsStar", False)
  140. FileUtils.Copy(OverridesIni, VersionIni) '覆写已有的 ini
  141. Else
  142. WriteIni(VersionIni, "VersionArgumentIndie", 1) '开启版本隔离
  143. WriteIni(VersionIni, "VersionArgumentIndieV2", True)
  144. End If
  145. IniClearCache(VersionIni) '重置缓存,避免被安装过程中写入的 ini 覆盖
  146. End Sub
  147. ''' <summary>
  148. ''' 弹窗提示整合包中存在不兼容的加载器。
  149. ''' 如果取消,则抛出 CancelledException。
  150. ''' </summary>
  151. Private Sub NotifyIncompatibleLoader(LoaderName As String)
  152. If MyMsgBox($"整合包中存在不兼容的加载器:{LoaderName}{vbCrLf}如果你知道如何手动安装它,可以先选择跳过,然后在整合包下载结束后手动安装。",
  153. "不兼容的加载器", "取消", $"不安装 {LoaderName} 并继续") = 1 Then Throw New OperationCanceledException
  154. End Sub
  155. #Region "不同类型整合包的安装方法"
  156. 'CurseForge
  157. Private Function InstallPackCurseForge(FileAddress As String, Archive As ZipArchive, ArchiveBaseFolder As String,
  158. Optional InstanceName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String)
  159. '读取 Json 文件
  160. Dim Json As JObject
  161. Try
  162. Json = Archive.GetEntry(ArchiveBaseFolder & "manifest.json").Open().ReadString().DeserializeJson()
  163. Catch ex As Exception
  164. Throw New Exception("CurseForge 整合包安装信息存在问题", ex)
  165. End Try
  166. If Json("minecraft") Is Nothing OrElse Json("minecraft")("version") Is Nothing Then Throw New Exception("CurseForge 整合包未提供 Minecraft 版本信息")
  167. '获取版本名
  168. If InstanceName Is Nothing Then
  169. InstanceName = If(Json("name"), "")
  170. Dim Validate As New ValidateFolderName(McFolderSelected & "versions")
  171. If Validate.Validate(InstanceName) <> "" Then InstanceName = ""
  172. If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate})
  173. If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException
  174. End If
  175. Dim VersionFolder As String = PathUtils.AddSlashSuffix(Path.Combine(McFolderSelected, "versions", InstanceName) & "\")
  176. '获取 Mod API 版本信息
  177. Dim TargetVersion As New McVersion(Json("minecraft")("version").ToString)
  178. For Each Entry In If(Json("minecraft")("modLoaders"), {})
  179. Dim Id As String = If(Entry("id"), "").ToString.Lower
  180. If Id.StartsWithF("forge-") Then
  181. 'Forge 指定
  182. If Id.Contains("recommended") Then Throw New Exception("该整合包版本过老,已不支持进行安装!")
  183. Logger.Info($"整合包 Forge 版本:{Id}")
  184. TargetVersion.ModLoaderVersions(ModLoaders.Forge) = Id.Replace("forge-", "")
  185. ElseIf Id.StartsWithF("neoforge-") Then
  186. 'NeoForge 指定
  187. Logger.Info($"整合包 NeoForge 版本:{Id}")
  188. TargetVersion.ModLoaderVersions(ModLoaders.NeoForge) = Id.Replace("neoforge-", "")
  189. ElseIf Id.StartsWithF("fabric-") Then
  190. 'Fabric 指定
  191. Logger.Info($"整合包 Fabric 版本:{Id}")
  192. TargetVersion.ModLoaderVersions(ModLoaders.Fabric) = Id.Replace("fabric-", "")
  193. Else
  194. NotifyIncompatibleLoader(Id)
  195. End If
  196. Next
  197. '解压
  198. Dim InstallTemp As String = RequestTaskTempFolder()
  199. Dim InstallLoaders As New List(Of LoaderBase)
  200. Dim OverrideHome As String = If(Json("overrides"), "")
  201. If OverrideHome <> "" Then
  202. Dim OverridesFolder As String = InstallTemp & ArchiveBaseFolder & If(OverrideHome = "." OrElse OverrideHome = "./", "", OverrideHome) '#5613
  203. If Not PathUtils.IsParentOf(InstallTemp, OverridesFolder) Then Throw New Exception("整合包的 overrides 路径超出了解压目录,请向整合包作者反馈此问题!")
  204. InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
  205. Sub(Task As LoaderTask(Of String, Integer))
  206. ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.6))
  207. CopyOverrideDirectory(OverridesFolder, VersionFolder, Task)
  208. End Sub) With {
  209. .ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
  210. End If
  211. '获取 Mod 列表
  212. Dim ModList As New List(Of Integer)
  213. Dim ModOptionalList As New List(Of Integer)
  214. For Each ModEntry In If(Json("files"), {})
  215. If ModEntry("projectID") Is Nothing OrElse ModEntry("fileID") Is Nothing Then
  216. Hint("某项 Mod 缺少必要信息,已跳过:" & ModEntry.ToString)
  217. Continue For
  218. End If
  219. ModList.Add(ModEntry("fileID"))
  220. If ModEntry("required") IsNot Nothing AndAlso Not ModEntry("required").ToObject(Of Boolean) Then ModOptionalList.Add(ModEntry("fileID"))
  221. Next
  222. If ModList.Any Then
  223. Dim ModDownloadLoaders As New List(Of LoaderBase)
  224. '获取 Mod 下载信息
  225. ModDownloadLoaders.Add(New LoaderTask(Of Integer, JArray)("获取 Mod 下载信息",
  226. Sub(Task As LoaderTask(Of Integer, JArray))
  227. Task.Output = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, "{""fileIds"": [" & ModList.Join(",") & "]}", "application/json")("data")
  228. If ModList.Count = Task.Output.Count Then Return
  229. '如果文件已被删除,则 API 会跳过那一项,导致返回的列表数量小于请求的数量。后面的都是对此的错误处理。
  230. '获取缺失的 fileId 列表
  231. Dim MissingIds = ModList.Except(Task.Output.Select(Function(j) j("id").ToObject(Of Integer))).ToList
  232. Logger.Info($"缺失的 fileIds:{MissingIds.Join("、")}")
  233. '将 fileId 映射到 projectId
  234. Dim Missings = MissingIds.Select(Of (ProjectId As Integer, FileId As Integer))(
  235. Function(id) (CType(Json("files").AsEnumerable.First(Function(f) f("fileID").ToObject(Of Integer) = id), JObject)("projectID").ToObject(Of Integer), id)).ToList
  236. Logger.Info($"缺失项:{Missings.Join("、")}")
  237. '尝试获取对应的工程信息
  238. Dim Projects As New List(Of ResourceProject)
  239. Try
  240. Projects = ResourceProject.FromProjectIds(Missings.Select(Function(e) e.ProjectId.ToString), ResourcePlatforms.CurseForge).ToList
  241. Catch ex As Exception
  242. Logger.Warn(ex, "获取缺失的工程信息失败")
  243. End Try
  244. Dim MissingDesc As New List(Of String)
  245. For Each Missing In Missings
  246. Dim Project = Projects.FirstOrDefault(Function(p) p.Id = Missing.ProjectId)
  247. If Project Is Nothing Then
  248. MissingDesc.Add($"未知 CurseForge 项目 (项目 ID {Missing.ProjectId},文件 ID {Missing.FileId})")
  249. Else
  250. MissingDesc.Add($"{Project.TranslatedName} (文件 ID {Missing.FileId})")
  251. End If
  252. Next
  253. If MyMsgBox($"未找到该整合包所需的以下内容,它们可能已被原作者删除:{vbCrLf}- {MissingDesc.Join(vbCrLf + "- ")}{vbCrLf}{vbCrLf}是否继续安装?缺失这些内容可能导致整合包出现异常!",
  254. "整合包内容缺失", "继续", "取消", IsWarn:=True) = 2 Then
  255. Task.Cancel()
  256. End If
  257. End Sub) With {.ProgressWeight = ModList.Count / 10}) '每 10 Mod 需要 1s
  258. '构造 NetFile
  259. ModDownloadLoaders.Add(New LoaderTask(Of JArray, List(Of NetFile))("构造 Mod 下载信息",
  260. Sub(Task As LoaderTask(Of JArray, List(Of NetFile)))
  261. Dim FileList As New Dictionary(Of Integer, NetFile)
  262. For Each ModJson In Task.Input
  263. Dim Id As Integer = ModJson("id").ToObject(Of Integer)
  264. '跳过重复的 Mod(疑似 CurseForge Bug)
  265. If FileList.ContainsKey(Id) Then Continue For
  266. '可选 Mod 提示
  267. If ModOptionalList.Contains(Id) Then
  268. If MyMsgBox("是否要下载整合包中的可选文件 " & ModJson("displayName").ToString & "?", "下载可选文件", "是", "否") = 2 Then
  269. Continue For
  270. End If
  271. End If
  272. '根据 modules 和文件名后缀判断资源类型
  273. Dim TargetFolder As String, Type As ResourceTypes
  274. If ModJson("modules").Any Then 'modules 可能返回 null(#1006)
  275. Dim ModuleNames = CType(ModJson("modules"), JArray).Select(Function(l) l("name").ToString).ToList
  276. If ModuleNames.Contains("META-INF") OrElse ModuleNames.Contains("mcmod.info") OrElse ModuleNames.Contains("fabric.mod.json") OrElse
  277. ModJson?("FileName")?.ToString.EndsWithF(".jar", True) Then
  278. TargetFolder = "mods" : Type = ResourceTypes.Mod
  279. ElseIf ModuleNames.Contains("pack.mcmeta") Then
  280. TargetFolder = "resourcepacks" : Type = ResourceTypes.ResourcePack
  281. ElseIf ModuleNames.Contains("shaders") Then
  282. TargetFolder = "shaderpacks" : Type = ResourceTypes.Shader
  283. Else
  284. TargetFolder = "saves" : Type = ResourceTypes.Map
  285. End If
  286. Else
  287. TargetFolder = "mods" : Type = ResourceTypes.Mod
  288. End If
  289. '建立 CompFile
  290. Dim File = ResourceVersion.FromPlatformJson(ModJson, Type)
  291. If Not File.DownloadAvailable Then Continue For
  292. '实际的添加
  293. FileList.Add(Id, File.ToNetFile(PathUtils.AddSlashSuffix(Path.Combine(VersionFolder, TargetFolder)),
  294. ResourceVersion.DownloadReason.ModPack, TargetVersion.VanillaName, TargetVersion.ModLoaders))
  295. Task.Progress += 1 / (1 + ModList.Count)
  296. Next
  297. Task.Output = FileList.Values.ToList
  298. End Sub) With {.ProgressWeight = ModList.Count / 200, .Show = False}) '每 200 Mod 需要 1s
  299. '下载 Mod 文件
  300. ModDownloadLoaders.Add(New LoaderDownload("下载 Mod", New List(Of NetFile)) With {.ProgressWeight = ModList.Count * 1.5}) '每个 Mod 需要 1.5s
  301. '构造加载器
  302. InstallLoaders.Add(New LoaderCombo(Of Integer)("下载 Mod(主加载器)", ModDownloadLoaders) With
  303. {.Show = False, .ProgressWeight = ModDownloadLoaders.Sum(Function(l) l.ProgressWeight)})
  304. End If
  305. '构造加载器
  306. Dim Target As New McComponents(TargetVersion.VanillaName, TargetVersion.ModLoaderVersions)
  307. Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With {
  308. .NewInstanceName = InstanceName, .InstanceFolder = VersionFolder, .Components = Target})
  309. '构造总加载器
  310. Dim Loaders As New List(Of LoaderBase)
  311. Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
  312. Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
  313. Loaders.Add(New LoaderTask(Of String, String)("最终整理文件",
  314. Sub(Task As LoaderTask(Of String, String))
  315. '设置图标
  316. If Logo IsNot Nothing AndAlso FileUtils.Exists(Logo) Then
  317. FileUtils.Copy(Logo, VersionFolder & "PCL\Logo.png")
  318. WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png")
  319. WriteIni(VersionFolder & "PCL\Setup.ini", "LogoCustom", "True")
  320. Logger.Info($"已设置整合包 Logo:{Logo}")
  321. End If
  322. '解压地图文件
  323. Dim SaveFolder As String = $"{VersionFolder}saves\"
  324. For Each MapFile In DirectoryUtils.EnumerateFiles(SaveFolder, searchPattern:="*.zip")
  325. FileUtils.ExtractToDirectory(MapFile, SaveFolder, Task.CreateCancellationToken())
  326. FileUtils.Delete(MapFile)
  327. Next
  328. '删除原始整合包文件
  329. For Each FileToDelete As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
  330. If FileUtils.Exists(FileToDelete) Then
  331. Logger.Info($"删除原始整合包文件:{FileToDelete}")
  332. FileUtils.Delete(FileToDelete)
  333. End If
  334. Next
  335. If FileUtils.Exists(FileAddress) AndAlso PathUtils.GetFileNameWithoutExtension(FileAddress) = "modpack" Then
  336. Logger.Info($"删除安装整合包文件:{FileAddress}")
  337. FileUtils.Delete(FileAddress)
  338. End If
  339. End Sub) With {.ProgressWeight = 0.1, .Show = False})
  340. '重复任务检查
  341. Dim LoaderName As String = "CurseForge 整合包安装:" & InstanceName & " "
  342. If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
  343. Hint("该整合包正在安装中!", HintType.Red)
  344. Throw New OperationCanceledException
  345. End If
  346. '启动
  347. Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
  348. Loader.Start(VersionFolder)
  349. LoaderTaskbarAdd(Loader)
  350. FrmMain.BtnExtraDownload.ShowRefresh()
  351. Return Loader
  352. End Function
  353. 'Modrinth
  354. Private Function InstallPackModrinth(FileAddress As String, Archive As ZipArchive, ArchiveBaseFolder As String, Optional InstanceName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String)
  355. '读取 Json 文件
  356. Dim Json As JObject
  357. Try
  358. Json = Archive.GetEntry(ArchiveBaseFolder & "modrinth.index.json").Open().ReadString().DeserializeJson()
  359. Catch ex As Exception
  360. Throw New Exception("Modrinth 整合包安装信息存在问题", ex)
  361. End Try
  362. If Json("dependencies") Is Nothing OrElse Json("dependencies")("minecraft") Is Nothing Then Throw New Exception("Modrinth 整合包未提供 Minecraft 版本信息")
  363. '获取 Mod API 版本信息
  364. 'TODO: 改为使用类似 InstallPackMMC 中,一个版本名一个字典的方式来存储它,不要用 Unknown
  365. Dim TargetVersion As New McVersion("Unknown")
  366. For Each Entry As JProperty In If(Json("dependencies"), {})
  367. Select Case Entry.Name.Lower
  368. Case "minecraft"
  369. TargetVersion.VanillaName = Entry.Value.ToString
  370. Case "forge" 'eg. 14.23.5.2859 / 1.19-41.1.0
  371. TargetVersion.ModLoaderVersions(ModLoaders.Forge) = Entry.Value.ToString
  372. Logger.Info($"整合包 Forge 版本:{TargetVersion.ModLoaderVersions(ModLoaders.Forge)}")
  373. Case "neoforge", "neo-forge" 'eg. 20.6.98-beta
  374. TargetVersion.ModLoaderVersions(ModLoaders.NeoForge) = Entry.Value.ToString
  375. Logger.Info($"整合包 NeoForge 版本:{TargetVersion.ModLoaderVersions(ModLoaders.NeoForge)}")
  376. Case "fabric-loader" 'eg. 0.14.14
  377. TargetVersion.ModLoaderVersions(ModLoaders.Fabric) = Entry.Value.ToString
  378. Logger.Info($"整合包 Fabric 版本:{TargetVersion.ModLoaderVersions(ModLoaders.Fabric)}")
  379. Case Else
  380. NotifyIncompatibleLoader(Entry.Name)
  381. End Select
  382. Next
  383. '获取版本名
  384. If InstanceName Is Nothing Then
  385. InstanceName = If(Json("name"), "")
  386. Dim Validate As New ValidateFolderName(McFolderSelected & "versions")
  387. If Validate.Validate(InstanceName) <> "" Then InstanceName = ""
  388. If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate})
  389. If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException
  390. End If
  391. Dim VersionFolder As String = Path.Combine(McFolderSelected, "versions", InstanceName) & "\"
  392. '解压
  393. Dim InstallTemp As String = RequestTaskTempFolder()
  394. Dim InstallLoaders As New List(Of LoaderBase)
  395. InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
  396. Sub(Task As LoaderTask(Of String, Integer))
  397. ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.5))
  398. CopyOverrideDirectory(InstallTemp & ArchiveBaseFolder & "overrides", VersionFolder, Task)
  399. CopyOverrideDirectory(InstallTemp & ArchiveBaseFolder & "client-overrides", VersionFolder, Task)
  400. End Sub) With {.ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
  401. '获取下载文件列表
  402. Dim FileList As New List(Of NetFile)
  403. For Each File In If(Json("files"), {})
  404. '检查是否需要该文件
  405. If File("env") IsNot Nothing Then
  406. Select Case File("env")("client").ToString
  407. Case "optional"
  408. If MyMsgBox("是否要下载可选文件 " & PathUtils.GetLastPart(File("path").ToString) & "?",
  409. "下载可选文件", "是", "否") = 2 Then
  410. Continue For
  411. End If
  412. Case "unsupported"
  413. Continue For
  414. End Select
  415. End If
  416. '添加下载文件
  417. Dim Urls = File("downloads").SelectMany(Function(t) ResourceVersion.ParseCurseForgeDownloadUrls(t.ToString)).ToList
  418. Urls.AddRange(Urls.Select(Function(u) DlSourceModGet(u)).ToList)
  419. Urls = ResourceVersion.ParseModrinthTrackArguments(Urls,
  420. ResourceVersion.DownloadReason.ModPack, TargetVersion.VanillaName, TargetVersion.ModLoaders).ToList 'Modrinth 来源追踪信息
  421. Urls = Urls.Distinct.ToList()
  422. Dim TargetPath As String = Path.Combine(VersionFolder, File("path").ToString().TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
  423. If Not PathUtils.IsParentOf(VersionFolder, TargetPath) Then
  424. MyMsgBox($"整合包的文件路径超出了版本文件夹,请向整合包作者反馈此问题!{vbCrLf}目标:{Path.GetFullPath(TargetPath)}{vbCrLf}错误的文件:{TargetPath}", "文件路径校验失败", IsWarn:=True)
  425. Throw New OperationCanceledException
  426. End If
  427. FileList.Add(New NetFile(Urls, TargetPath,
  428. New FileChecker With {.Hash = File("hashes")("sha1").ToString}, True)) 'Modrinth 整合包不应校验文件大小(他们官方自己说的)
  429. Next
  430. If FileList.Any Then
  431. InstallLoaders.Add(New LoaderDownload("下载额外文件", FileList) With {.ProgressWeight = FileList.Count * 1.5}) '每个 Mod 需要 1.5s
  432. End If
  433. '构造加载器
  434. Dim Target As New McComponents(TargetVersion.VanillaName, TargetVersion.ModLoaderVersions)
  435. Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With {
  436. .NewInstanceName = InstanceName, .InstanceFolder = VersionFolder,
  437. .Components = Target})
  438. '构造总加载器
  439. Dim Loaders As New List(Of LoaderBase)
  440. Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
  441. Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
  442. Loaders.Add(New LoaderTask(Of String, String)("最终整理文件",
  443. Sub(Task As LoaderTask(Of String, String))
  444. '设置图标
  445. If Logo IsNot Nothing AndAlso FileUtils.Exists(Logo) Then
  446. FileUtils.Copy(Logo, VersionFolder & "PCL\Logo.png")
  447. WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png")
  448. WriteIni(VersionFolder & "PCL\Setup.ini", "LogoCustom", "True")
  449. Logger.Info($"已设置整合包 Logo:{Logo}")
  450. End If
  451. '删除原始整合包文件
  452. For Each FileToDelete As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
  453. If FileUtils.Exists(FileToDelete) Then
  454. Logger.Info($"删除原始整合包文件:{FileToDelete}")
  455. FileUtils.Delete(FileToDelete)
  456. End If
  457. Next
  458. If FileUtils.Exists(FileAddress) AndAlso PathUtils.GetFileNameWithoutExtension(FileAddress) = "modpack" Then
  459. Logger.Info($"删除安装整合包文件:{FileAddress}")
  460. FileUtils.Delete(FileAddress)
  461. End If
  462. End Sub) With {.ProgressWeight = 0.1, .Show = False})
  463. '重复任务检查
  464. Dim LoaderName As String = $"Modrinth 整合包安装:{InstanceName} "
  465. If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
  466. Hint("该整合包正在安装中!", HintType.Red)
  467. Throw New OperationCanceledException
  468. End If
  469. '启动
  470. Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
  471. Loader.Start(VersionFolder)
  472. LoaderTaskbarAdd(Loader)
  473. FrmMain.BtnExtraDownload.ShowRefresh()
  474. Return Loader
  475. End Function
  476. 'HMCL
  477. Private Function InstallPackHMCL(FileAddress As String, Archive As ZipArchive, ArchiveBaseFolder As String) As LoaderCombo(Of String)
  478. '读取 Json 文件
  479. Dim Json As JObject
  480. Try
  481. Json = Archive.GetEntry(ArchiveBaseFolder & "modpack.json").Open().ReadString(Encoding.UTF8).DeserializeJson()
  482. Catch ex As Exception
  483. Throw New Exception("HMCL 整合包安装信息存在问题", ex)
  484. End Try
  485. '获取版本名
  486. Dim InstanceName As String = If(Json("name"), "")
  487. Dim Validate As New ValidateFolderName(McFolderSelected & "versions")
  488. If Validate.Validate(InstanceName) <> "" Then InstanceName = ""
  489. If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate})
  490. If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException
  491. Dim VersionFolder As String = Path.Combine(McFolderSelected, "versions", InstanceName) & "\"
  492. '解压
  493. Dim InstallTemp As String = RequestTaskTempFolder()
  494. Dim InstallLoaders As New List(Of LoaderBase)
  495. InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
  496. Sub(Task As LoaderTask(Of String, Integer))
  497. ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.6))
  498. CopyOverrideDirectory(InstallTemp & ArchiveBaseFolder & "minecraft", VersionFolder, Task)
  499. End Sub) With {.ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
  500. '构造游戏本体安装加载器
  501. If Json("gameVersion") Is Nothing Then Throw New Exception("该 HMCL 整合包未提供游戏版本信息,无法安装!")
  502. Dim Target As New McComponents(Json("gameVersion").ToString, New Dictionary(Of ModLoaders, String))
  503. Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With {
  504. .NewInstanceName = InstanceName, .InstanceFolder = VersionFolder,
  505. .Components = Target})
  506. '构造总加载器
  507. Dim Loaders As New List(Of LoaderBase) From {
  508. New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)},
  509. New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)}
  510. }
  511. '重复任务检查
  512. Dim LoaderName As String = "HMCL 整合包安装:" & InstanceName & " "
  513. If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
  514. Hint("该整合包正在安装中!", HintType.Red)
  515. Throw New OperationCanceledException
  516. End If
  517. '启动
  518. Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
  519. Loader.Start(VersionFolder)
  520. LoaderTaskbarAdd(Loader)
  521. FrmMain.BtnExtraDownload.ShowRefresh()
  522. Return Loader
  523. End Function
  524. 'MMC
  525. Private Function InstallPackMMC(FileAddress As String, Archive As ZipArchive, ArchiveBaseFolder As String) As LoaderCombo(Of String)
  526. '读取 Json 文件
  527. Dim PackJson As JObject, PackInstance As String
  528. Try
  529. PackJson = Archive.GetEntry(ArchiveBaseFolder & "mmc-pack.json").Open().ReadString(Encoding.UTF8).DeserializeJson()
  530. PackInstance = Archive.GetEntry(ArchiveBaseFolder & "instance.cfg").Open().ReadString(Encoding.UTF8)
  531. Catch ex As Exception
  532. Throw New Exception("MMC 整合包安装信息存在问题", ex)
  533. End Try
  534. '获取版本名
  535. Dim InstanceName As String = If(PackInstance.RegexSeek("(?<=\nname\=)[^\n]+"), "")
  536. Dim Validate As New ValidateFolderName(McFolderSelected & "versions")
  537. If Validate.Validate(InstanceName) <> "" Then InstanceName = ""
  538. If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate})
  539. If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException
  540. Dim VersionFolder As String = Path.Combine(McFolderSelected, "versions", InstanceName) & "\"
  541. '解压
  542. Dim InstallTemp As String = RequestTaskTempFolder()
  543. Dim SetupFile As String = VersionFolder & "PCL\Setup.ini"
  544. Dim InstallLoaders As New List(Of LoaderBase)
  545. InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
  546. Sub(Task As LoaderTask(Of String, Integer))
  547. ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.55))
  548. 'Prism 优先使用 minecraft/ 而非 .minecraft/(#9272)
  549. Dim MmcGameRoot As String
  550. If DirectoryUtils.Exists(InstallTemp & ArchiveBaseFolder & ".minecraft") AndAlso Not DirectoryUtils.Exists(InstallTemp & ArchiveBaseFolder & "minecraft") Then
  551. MmcGameRoot = InstallTemp & ArchiveBaseFolder & ".minecraft"
  552. Else
  553. MmcGameRoot = InstallTemp & ArchiveBaseFolder & "minecraft"
  554. End If
  555. CopyOverrideDirectory(MmcGameRoot, VersionFolder, Task)
  556. '读取 MMC 设置文件(#2655)
  557. Try
  558. Dim MmcSetupFile As String = InstallTemp & ArchiveBaseFolder & "instance.cfg"
  559. If FileUtils.Exists(MmcSetupFile) Then
  560. '将其中的等号替换为冒号,以符合 ini 文件格式
  561. Dim Lines As New List(Of String)
  562. For Each Line In FileUtils.ReadAsLines(MmcSetupFile, True)
  563. If Not Line.Contains("=") Then Continue For
  564. Lines.Add(Line.BeforeFirst("=") & ":" & Line.AfterFirst("="))
  565. Next
  566. FileUtils.Write(MmcSetupFile, Lines.Join(vbCrLf))
  567. '读取文件
  568. If ReadIni(MmcSetupFile, "OverrideCommands", False) Then
  569. Dim PreLaunchCommand As String = ReadIni(MmcSetupFile, "PreLaunchCommand")
  570. If PreLaunchCommand <> "" Then
  571. PreLaunchCommand = PreLaunchCommand.Replace("\""", """").
  572. Replace("$INST_JAVA", "{java}java.exe").
  573. Replace("$INST_MC_DIR\", "{minecraft}").Replace("$INST_MC_DIR", "{minecraft}").
  574. Replace("$INST_DIR\", "{verpath}").Replace("$INST_DIR", "{verpath}").
  575. Replace("$INST_ID", "{name}").Replace("$INST_NAME", "{name}")
  576. WriteIni(SetupFile, "VersionAdvanceRun", PreLaunchCommand)
  577. Logger.Info($"迁移 MultiMC 版本独立设置:启动前执行命令:{PreLaunchCommand}")
  578. End If
  579. End If
  580. If ReadIni(MmcSetupFile, "JoinServerOnLaunch", False) Then
  581. Dim ServerAddress As String = ReadIni(MmcSetupFile, "JoinServerOnLaunchAddress").Replace("\""", """")
  582. WriteIni(SetupFile, "VersionServerEnter", ServerAddress)
  583. Logger.Info($"迁移 MultiMC 版本独立设置:自动进入服务器:{ServerAddress}")
  584. End If
  585. Dim Logo As String = ReadIni(MmcSetupFile, "iconKey", "")
  586. If Logo <> "" AndAlso FileUtils.Exists($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png") Then
  587. WriteIni(SetupFile, "LogoCustom", True)
  588. WriteIni(SetupFile, "Logo", "PCL\Logo.png")
  589. FileUtils.Copy($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png", VersionFolder & "PCL\Logo.png")
  590. Logger.Info($"迁移 MultiMC 版本独立设置:版本图标({Logo}.png)")
  591. End If
  592. 'JVM 参数
  593. Dim JvmArgs As String = ReadIni(MmcSetupFile, "JvmArgs", "")
  594. If JvmArgs <> "" Then
  595. If ReadIni(MmcSetupFile, "OverrideJavaArgs", False) Then
  596. WriteIni(SetupFile, "VersionAdvanceJvm", JvmArgs)
  597. Logger.Info($"迁移 MultiMC 版本独立设置:JVM 参数(覆盖):{JvmArgs}")
  598. Else
  599. JvmArgs += " " & Settings.Get(Of String)("LaunchAdvanceJvm")
  600. WriteIni(SetupFile, "VersionAdvanceJvm", JvmArgs)
  601. Logger.Info($"迁移 MultiMC 版本独立设置:JVM 参数(追加):{JvmArgs}")
  602. End If
  603. End If
  604. End If
  605. Catch ex As Exception
  606. Logger.Warn(ex, $"读取 MMC 配置文件失败({InstallTemp}{ArchiveBaseFolder}instance.cfg)")
  607. End Try
  608. End Sub) With {.ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
  609. '构造版本安装请求
  610. If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!")
  611. Dim MinecraftVersion As String = Nothing
  612. Dim ModLoaderVersions As New Dictionary(Of ModLoaders, String)
  613. For Each Component As JObject In PackJson("components")
  614. If Not Component.ContainsKey("uid") Then Continue For
  615. Dim UID As String = Component("uid").ToString
  616. Select Case UID
  617. Case "net.minecraft"
  618. MinecraftVersion = Component("version")
  619. Case "net.minecraftforge"
  620. ModLoaderVersions(ModLoaders.Forge) = Component("version")
  621. Case "net.neoforged"
  622. ModLoaderVersions(ModLoaders.NeoForge) = Component("version")
  623. Case "net.fabricmc.fabric-loader"
  624. ModLoaderVersions(ModLoaders.Fabric) = Component("version")
  625. Case Else
  626. If UID.StartsWithF("org.lwjgl") Then '#8210
  627. Logger.Info($"已跳过 LWJGL 项:{UID}")
  628. Else
  629. NotifyIncompatibleLoader(UID)
  630. End If
  631. End Select
  632. Next
  633. '构造加载器
  634. Dim Target As New McComponents(MinecraftVersion, ModLoaderVersions)
  635. Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With {
  636. .NewInstanceName = InstanceName, .InstanceFolder = VersionFolder,
  637. .Components = Target})
  638. '构造总加载器
  639. Dim Loaders As New List(Of LoaderBase)
  640. Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
  641. Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
  642. '重复任务检查
  643. Dim LoaderName As String = "MMC 整合包安装:" & InstanceName & " "
  644. If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
  645. Hint("该整合包正在安装中!", HintType.Red)
  646. Throw New OperationCanceledException
  647. End If
  648. '启动
  649. Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
  650. Loader.Start(VersionFolder)
  651. LoaderTaskbarAdd(Loader)
  652. FrmMain.BtnExtraDownload.ShowRefresh()
  653. Return Loader
  654. End Function
  655. 'MCBBS
  656. Private Function InstallPackMCBBS(FileAddress As String, Archive As ZipArchive, ArchiveBaseFolder As String,
  657. Optional InstanceName As String = Nothing) As LoaderCombo(Of String)
  658. '读取 Json 文件
  659. Dim Json As JObject
  660. Try
  661. Dim Entry = If(Archive.GetEntry(ArchiveBaseFolder & "mcbbs.packmeta"), Archive.GetEntry(ArchiveBaseFolder & "manifest.json"))
  662. Json = Entry.Open().ReadString(Encoding.UTF8).DeserializeJson()
  663. Catch ex As Exception
  664. Throw New Exception("MCBBS 整合包安装信息存在问题", ex)
  665. End Try
  666. '获取版本名
  667. If InstanceName Is Nothing Then
  668. InstanceName = If(Json("name"), "")
  669. Dim Validate As New ValidateFolderName(McFolderSelected & "versions")
  670. If Validate.Validate(InstanceName) <> "" Then InstanceName = ""
  671. If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate})
  672. If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException
  673. End If
  674. Dim VersionFolder As String = Path.Combine(McFolderSelected, "versions", InstanceName) & "\"
  675. '解压
  676. Dim InstallTemp As String = RequestTaskTempFolder()
  677. Dim SetupFile As String = VersionFolder & "PCL\Setup.ini"
  678. Dim InstallLoaders As New List(Of LoaderBase)
  679. InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
  680. Sub(Task As LoaderTask(Of String, Integer))
  681. ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.6))
  682. CopyOverrideDirectory(InstallTemp & ArchiveBaseFolder & "overrides", VersionFolder, Task)
  683. 'JVM 参数
  684. If Json("launchInfo") IsNot Nothing Then
  685. Dim LaunchInfo As JObject = Json("launchInfo")
  686. If LaunchInfo.ContainsKey("javaArgument") Then WriteIni(SetupFile, "VersionAdvanceJvm", String.Join(" ", LaunchInfo("javaArgument")))
  687. If LaunchInfo.ContainsKey("launchArgument") Then WriteIni(SetupFile, "VersionAdvanceGame", String.Join(" ", LaunchInfo("launchArgument")))
  688. End If
  689. End Sub) With {.ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
  690. '构造加载器
  691. If Json("addons") Is Nothing Then Throw New Exception("该 MCBBS 整合包未提供游戏版本附加信息,无法安装!")
  692. Dim Addons As New Dictionary(Of String, String)
  693. For Each Entry In Json("addons")
  694. Addons.Add(Entry("id"), Entry("version"))
  695. Next
  696. If Not Addons.ContainsKey("game") Then Throw New Exception("该 MCBBS 整合包未提供游戏版本信息,无法安装!")
  697. If Addons.ContainsKey("quilt") Then
  698. Hint("PCL 不支持安装 Quilt 整合包!", HintType.Red)
  699. Throw New OperationCanceledException
  700. End If
  701. Dim ModLoaderVersions As New Dictionary(Of ModLoaders, String)
  702. If Addons.ContainsKey("forge") Then ModLoaderVersions(ModLoaders.Forge) = Addons("forge")
  703. If Addons.ContainsKey("neoforge") Then ModLoaderVersions(ModLoaders.NeoForge) = Addons("neoforge")
  704. If Addons.ContainsKey("fabric") Then ModLoaderVersions(ModLoaders.Fabric) = Addons("fabric")
  705. Dim OptiFineEntry As JObject = Nothing
  706. If Addons.ContainsKey("optifine") Then
  707. Dim MinecraftVersion = Addons("game")
  708. If MinecraftVersion.EndsWithF(".0") Then MinecraftVersion = MinecraftVersion.Substring(0, MinecraftVersion.Length - 2)
  709. Dim OptiFineFileSuffix = "_HD_" & Addons("optifine").AfterLast("HD_") & ".jar"
  710. '从镜像源拉取 OptiFine 列表
  711. If DlOptiFineListBmclapiLoader.State <> LoadState.Finished Then DlOptiFineListBmclapiLoader.WaitForExit()
  712. Dim Entry = DlOptiFineListBmclapiLoader.Output.Value.FirstOrDefault(
  713. Function(v) v.Inherit = MinecraftVersion AndAlso v.FileName.EndsWithF(OptiFineFileSuffix))
  714. If Entry Is Nothing Then Throw New Exception($"OptiFine 列表中没有整合包所需的 OptiFine {Addons("optifine")}(Minecraft {Addons("game")})")
  715. OptiFineEntry = JObject.FromObject(Entry)
  716. End If
  717. Dim Target As New McComponents(Addons("game"), ModLoaderVersions, OptiFineEntry)
  718. Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With {
  719. .NewInstanceName = InstanceName, .InstanceFolder = VersionFolder,
  720. .Components = Target})
  721. '构造总加载器
  722. Dim Loaders As New List(Of LoaderBase)
  723. Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
  724. Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
  725. '重复任务检查
  726. Dim LoaderName As String = "MCBBS 整合包安装:" & InstanceName & " "
  727. If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
  728. Hint("该整合包正在安装中!", HintType.Red)
  729. Throw New OperationCanceledException
  730. End If
  731. '启动
  732. Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
  733. 'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件”
  734. Loader.Start(VersionFolder)
  735. LoaderTaskbarAdd(Loader)
  736. FrmMain.BtnExtraDownload.ShowRefresh()
  737. Return Loader
  738. End Function
  739. '带启动器的压缩包
  740. Private Function InstallPackLauncherPack(FileAddress As String, Archive As ZipArchive, ArchiveBaseFolder As String) As LoaderCombo(Of String)
  741. '获取解压路径
  742. MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True)
  743. Dim TargetFolder As String = Dialogs.SelectFolder("选择安装目标(必须是一个空文件夹)", False).FirstOrDefault
  744. If TargetFolder Is Nothing Then Throw New OperationCanceledException
  745. If Not DirectoryUtils.IsEmpty(TargetFolder) Then Hint("请选择一个空文件夹作为安装目标!", HintType.Red) : Throw New OperationCanceledException
  746. '解压
  747. Dim Loader As New LoaderCombo(Of String)("解压压缩包", {
  748. New LoaderTask(Of String, Integer)("解压压缩包",
  749. Sub(Task As LoaderTask(Of String, Integer))
  750. ExtractModpackFiles(TargetFolder, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.9))
  751. OpenExplorer(TargetFolder)
  752. Thread.Sleep(400) '避免文件争用
  753. '查找解压后的 exe 文件
  754. Dim Launcher As String = Nothing
  755. For Each ExeFile In DirectoryUtils.EnumerateFiles(TargetFolder, searchPattern:="*.exe")
  756. Dim Info = FileVersionInfo.GetVersionInfo(ExeFile)
  757. Logger.Info($"文件 {ExeFile} 的产品名标识为 {Info.ProductName}")
  758. If Info.ProductName = "Plain Craft Launcher" Then
  759. Launcher = ExeFile
  760. Logger.Info($"发现整合包附带的 PCL 启动器:{ExeFile}")
  761. ElseIf (Info.ProductName.ContainsIgnoreCase("Launcher") OrElse Info.ProductName.Contains("启动")) AndAlso
  762. Not Info.ProductName = "Plain Craft Launcher Admin Manager" Then
  763. If Launcher Is Nothing Then
  764. Launcher = ExeFile
  765. Logger.Info($"发现整合包附带的疑似第三方启动器:{ExeFile}")
  766. End If
  767. End If
  768. Next
  769. Task.Progress = 0.95
  770. '尝试使用附带的启动器打开
  771. If Launcher IsNot Nothing Then
  772. Logger.Info($"找到压缩包中附带的启动器:{Launcher}")
  773. If MyMsgBox($"整合包里似乎自带了启动器,是否换用它继续安装?{vbCrLf}即将打开:{Launcher}", "换用整合包启动器?", "换用", "不换用") = 1 Then
  774. StartProcess(Launcher, "--wait") '要求等待已有的 PCL 退出
  775. Logger.Info("为换用整合包中的启动器启动,强制结束程序")
  776. FrmMain.EndProgram(False)
  777. Return
  778. End If
  779. Else
  780. Logger.Info("未找到压缩包中附带的启动器")
  781. End If
  782. '加入文件夹列表
  783. Dim InstanceName As String = PathUtils.GetLastPart(TargetFolder)
  784. DirectoryUtils.Create(TargetFolder & ".minecraft\")
  785. PageSelectLeft.AddFolder(
  786. TargetFolder & ".minecraft\" & ArchiveBaseFolder.Replace("/", "\").TrimStart("\"), '格式例如:包裹文件夹\.minecraft\(最短为空字符串)
  787. InstanceName, False)
  788. '调用 modpack 文件进行安装
  789. Dim ModpackFile = DirectoryUtils.EnumerateFiles(TargetFolder, True, "modpack.*").First
  790. Logger.Info($"调用 modpack 文件继续安装:{ModpackFile}")
  791. ModpackInstall(ModpackFile)
  792. End Sub)
  793. })
  794. Loader.Start(TargetFolder)
  795. LoaderTaskbarAdd(Loader)
  796. FrmMain.BtnExtraDownload.ShowRefresh()
  797. FrmMain.BtnExtraDownload.Ribble()
  798. Return Loader
  799. End Function
  800. '普通压缩包
  801. Private Function InstallPackCompress(FileAddress As String, Archive As ZipArchive) As LoaderCombo(Of String)
  802. '尝试定位 .minecraft 文件夹:寻找形如 “/versions/XXX/XXX.json” 的路径
  803. Dim Match As Match = Nothing
  804. Dim Regex As New Regex("^.*\/(?=versions\/(?<ver>[^\/]+)\/(\k<ver>)\.json$)", RegexOptions.IgnoreCase)
  805. For Each Entry In Archive.Entries
  806. Dim EntryMatch = Regex.Match("/" & Entry.FullName)
  807. If EntryMatch.Success Then
  808. Match = EntryMatch
  809. Exit For
  810. End If
  811. Next
  812. If Match Is Nothing Then Throw New Exception("文件结构不匹配,这可能不是 Minecraft 客户端压缩包?") '没有匹配
  813. Dim ArchiveBaseFolder As String = Match.Value.Replace("/", "\").TrimStart("\") '格式例如:包裹文件夹\.minecraft\(最短为空字符串)
  814. Logger.Info($"检测到压缩包的 .minecraft 根目录:{ArchiveBaseFolder},命中的版本名:{Match.Groups(1).Value}")
  815. '获取解压路径
  816. MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True)
  817. Dim TargetFolder As String = Dialogs.SelectFolder("选择安装目标(必须是一个空文件夹)", False).FirstOrDefault
  818. If TargetFolder Is Nothing Then Throw New OperationCanceledException
  819. If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Red) : Throw New OperationCanceledException
  820. If Not DirectoryUtils.IsEmpty(TargetFolder) Then Hint("请选择一个空文件夹作为安装目标!", HintType.Red) : Throw New OperationCanceledException
  821. '解压
  822. Dim Loader As New LoaderCombo(Of String)("解压压缩包", {
  823. New LoaderTask(Of String, Integer)("解压压缩包",
  824. Sub(Task As LoaderTask(Of String, Integer))
  825. ExtractModpackFiles(TargetFolder, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.95))
  826. '加入文件夹列表
  827. PageSelectLeft.AddFolder(TargetFolder & ArchiveBaseFolder, PathUtils.GetLastPart(TargetFolder), False)
  828. Thread.Sleep(400) '避免文件争用
  829. RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.InstanceSelect))
  830. End Sub)
  831. }) With {.OnStateChanged = AddressOf McInstallState}
  832. Loader.Start(TargetFolder)
  833. LoaderTaskbarAdd(Loader)
  834. FrmMain.BtnExtraDownload.ShowRefresh()
  835. FrmMain.BtnExtraDownload.Ribble()
  836. Return Loader
  837. End Function
  838. #End Region
  839. End Module