ModModpack.vb 67 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200
  1. Imports System.IO.Compression
  2. Imports System.Linq.Expressions
  3. Imports NAudio.Wave.SampleProviders
  4. Imports PCL.ModLoader
  5. Public Module ModModpack
  6. #Region "安装"
  7. '触发整合包安装的外部接口
  8. ''' <summary>
  9. ''' 弹窗要求选择一个整合包文件并进行安装。
  10. ''' </summary>
  11. Public Sub ModpackInstall()
  12. Dim File As String = SelectFile("整合包文件(*.rar;*.zip;*.mrpack)|*.rar;*.zip;*.mrpack", "选择整合包压缩文件") '选择整合包文件
  13. If String.IsNullOrEmpty(File) Then Exit Sub
  14. RunInThread(
  15. Sub()
  16. Try
  17. ModpackInstall(File)
  18. Catch ex As CancelledException
  19. Catch ex As Exception
  20. Log(ex, "手动安装整合包失败", LogLevel.Msgbox)
  21. End Try
  22. End Sub)
  23. End Sub
  24. ''' <summary>
  25. ''' 构建并启动安装给定的整合包文件的加载器,并返回该加载器。若失败则抛出异常。
  26. ''' 必须在工作线程执行。
  27. ''' </summary>
  28. ''' <exception cref="CancelledException" />
  29. Public Function ModpackInstall(File As String, Optional VersionName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String)
  30. Log("[ModPack] 整合包安装请求:" & If(File, "null"))
  31. Dim Archive As ZipArchive = Nothing
  32. Dim ArchiveBaseFolder As String = ""
  33. Try
  34. '获取整合包种类与关键 Json
  35. Dim PackType As Integer = -1
  36. Try
  37. Archive = New ZipArchive(New FileStream(File, FileMode.Open, FileAccess.Read, FileShare.Read))
  38. '从根目录判断整合包类型
  39. If Archive.GetEntry("mcbbs.packmeta") IsNot Nothing Then PackType = 3 : Exit Try 'MCBBS 整合包(优先于 manifest.json 判断)
  40. If Archive.GetEntry("mmc-pack.json") IsNot Nothing Then PackType = 2 : Exit Try 'MMC 整合包(优先于 manifest.json 判断,#4194)
  41. If Archive.GetEntry("modrinth.index.json") IsNot Nothing Then PackType = 4 : Exit Try 'Modrinth 整合包
  42. If Archive.GetEntry("manifest.json") IsNot Nothing Then
  43. Dim Json As JObject = GetJson(ReadFile(Archive.GetEntry("manifest.json").Open, Encoding.UTF8))
  44. If Json("addons") Is Nothing Then
  45. PackType = 0 : Exit Try 'CurseForge 整合包
  46. Else
  47. PackType = 3 : Exit Try 'MCBBS 整合包
  48. End If
  49. End If
  50. If Archive.GetEntry("modpack.json") IsNot Nothing Then PackType = 1 : Exit Try 'HMCL 整合包
  51. If Archive.GetEntry("modpack.zip") IsNot Nothing OrElse Archive.GetEntry("modpack.mrpack") IsNot Nothing Then PackType = 9 : Exit Try '带启动器的压缩包
  52. '从一级目录判断整合包类型
  53. For Each Entry In Archive.Entries
  54. Dim FullNames As String() = Entry.FullName.Split("/")
  55. ArchiveBaseFolder = FullNames(0) & "/"
  56. '确定为一级目录下
  57. If FullNames.Count <> 2 Then Continue For
  58. '判断是否为关键文件
  59. If FullNames(1) = "mcbbs.packmeta" Then PackType = 3 : Exit Try 'MCBBS 整合包(优先于 manifest.json 判断)
  60. If FullNames(1) = "mmc-pack.json" Then PackType = 2 : Exit Try 'MMC 整合包(优先于 manifest.json 判断,#4194)
  61. If FullNames(1) = "modrinth.index.json" Then PackType = 4 : Exit Try 'Modrinth 整合包
  62. If FullNames(1) = "manifest.json" Then
  63. Dim Json As JObject = GetJson(ReadFile(Entry.Open, Encoding.UTF8))
  64. If Json("addons") Is Nothing Then
  65. PackType = 0 : Exit Try 'CurseForge 整合包
  66. Else
  67. PackType = 3 : ArchiveBaseFolder = "overrides/" : Exit Try 'MCBBS 整合包
  68. End If
  69. End If
  70. If FullNames(1) = "modpack.json" Then PackType = 1 : Exit Try 'HMCL 整合包
  71. If FullNames(1) = "modpack.zip" OrElse FullNames(1) = "modpack.mrpack" Then PackType = 9 : Exit Try '带启动器的压缩包
  72. Next
  73. Catch ex As Exception
  74. If GetExceptionDetail(ex, True).Contains("Error.WinIOError") Then
  75. Throw New Exception("打开整合包文件失败", ex)
  76. ElseIf File.EndsWithF(".rar", True) Then
  77. Throw New Exception("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试", ex)
  78. Else
  79. Throw New Exception("打开整合包文件失败,文件可能损坏或为不支持的压缩包格式", ex)
  80. End If
  81. End Try
  82. '执行对应的安装方法
  83. Select Case PackType
  84. Case 0
  85. Log("[ModPack] 整合包种类:CurseForge")
  86. Return InstallPackCurseForge(File, Archive, ArchiveBaseFolder, VersionName, Logo)
  87. Case 1
  88. Log("[ModPack] 整合包种类:HMCL")
  89. Return InstallPackHMCL(File, Archive, ArchiveBaseFolder)
  90. Case 2
  91. Log("[ModPack] 整合包种类:MMC")
  92. Return InstallPackMMC(File, Archive, ArchiveBaseFolder)
  93. Case 3
  94. Log("[ModPack] 整合包种类:MCBBS")
  95. Return InstallPackMCBBS(File, Archive, ArchiveBaseFolder, VersionName)
  96. Case 4
  97. Log("[ModPack] 整合包种类:Modrinth")
  98. Return InstallPackModrinth(File, Archive, ArchiveBaseFolder, VersionName, Logo)
  99. Case 9
  100. Log("[ModPack] 整合包种类:带启动器的压缩包")
  101. Return InstallPackLauncherPack(File, Archive, ArchiveBaseFolder)
  102. Case Else
  103. Log("[ModPack] 整合包种类:未能识别,假定为压缩包")
  104. Return InstallPackCompress(File, Archive)
  105. End Select
  106. Finally
  107. If Archive IsNot Nothing Then Archive.Dispose()
  108. End Try
  109. End Function
  110. '整合包缓存清理
  111. Private IsInstallCacheCleared As Boolean = False
  112. Private IsInstallCacheClearing As Boolean = False
  113. Private Sub ExtractModpackFiles(InstallTemp As String, FileAddress As String, Loader As LoaderBase, LoaderProgressDelta As Double)
  114. '清理缓存文件夹
  115. If Not IsInstallCacheCleared Then
  116. IsInstallCacheCleared = True
  117. IsInstallCacheClearing = True
  118. Try
  119. Log("[ModPack] 开始清理整合包安装缓存")
  120. DeleteDirectory(PathTemp & "PackInstall\")
  121. Log("[ModPack] 已清理整合包安装缓存")
  122. Catch ex As Exception
  123. Log(ex, "清理整合包安装缓存失败")
  124. Finally
  125. IsInstallCacheClearing = False
  126. End Try
  127. ElseIf IsInstallCacheClearing Then
  128. '等待另一个整合包安装的清理步骤完成
  129. Do While IsInstallCacheClearing
  130. Thread.Sleep(1)
  131. Loop
  132. End If
  133. '解压文件
  134. Dim RetryCount As Integer = 1
  135. Dim Encode = Encoding.GetEncoding("GB18030")
  136. Try
  137. Retry:
  138. '完全不知道为啥会出现文件正在被另一进程使用的问题,总之多试试
  139. DeleteDirectory(InstallTemp)
  140. ExtractFile(FileAddress, InstallTemp, Encode, ProgressIncrementHandler:=Sub(Delta) Loader.Progress += Delta * LoaderProgressDelta)
  141. Catch ex As Exception
  142. Log(ex, "第 " & RetryCount & " 次解压尝试失败")
  143. If TypeOf ex Is ArgumentException Then
  144. Encode = Encoding.UTF8
  145. Log("[ModPack] 已切换压缩包解压编码为 UTF8")
  146. End If
  147. If RetryCount < 5 Then
  148. Thread.Sleep(RetryCount * 2000)
  149. If Loader IsNot Nothing AndAlso Loader.LoadingState <> MyLoading.MyLoadingState.Run Then Exit Sub
  150. RetryCount += 1
  151. GoTo Retry
  152. Else
  153. Throw New Exception("解压整合包文件失败", ex)
  154. End If
  155. End Try
  156. End Sub
  157. #Region "不同类型整合包的安装方法"
  158. 'CurseForge
  159. Private Function InstallPackCurseForge(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String,
  160. Optional VersionName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String)
  161. '读取 Json 文件
  162. Dim Json As JObject
  163. Try
  164. Json = GetJson(ReadFile(Archive.GetEntry(ArchiveBaseFolder & "manifest.json").Open))
  165. Catch ex As Exception
  166. Throw New Exception("CurseForge 整合包安装信息存在问题", ex)
  167. End Try
  168. If Json("minecraft") Is Nothing OrElse Json("minecraft")("version") Is Nothing Then Throw New Exception("CurseForge 整合包未提供 Minecraft 版本信息")
  169. '获取版本名
  170. If VersionName Is Nothing Then
  171. VersionName = If(Json("name"), "")
  172. Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
  173. If Validate.Validate(VersionName) <> "" Then VersionName = ""
  174. If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
  175. If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
  176. End If
  177. '获取 Mod API 版本信息
  178. Dim ForgeVersion As String = Nothing
  179. Dim NeoForgeVersion As String = Nothing
  180. Dim FabricVersion As String = Nothing
  181. For Each Entry In If(Json("minecraft")("modLoaders"), {})
  182. Dim Id As String = If(Entry("id"), "").ToString.ToLower
  183. If Id.StartsWithF("forge-") Then
  184. 'Forge 指定
  185. If Id.Contains("recommended") Then Throw New Exception("该整合包版本过老,已不支持进行安装!")
  186. Log("[ModPack] 整合包 Forge 版本:" & Id)
  187. ForgeVersion = Id.Replace("forge-", "")
  188. ElseIf Id.StartsWithF("neoforge-") Then
  189. 'NeoForge 指定
  190. Log("[ModPack] 整合包 NeoForge 版本:" & Id)
  191. NeoForgeVersion = Id.Replace("neoforge-", "")
  192. ElseIf Id.StartsWithF("fabric-") Then
  193. 'Fabric 指定
  194. Log("[ModPack] 整合包 Fabric 版本:" & Id)
  195. FabricVersion = Id.Replace("fabric-", "")
  196. Else
  197. Log("[ModPack] 未知 Mod 加载器:" & Id)
  198. End If
  199. Next
  200. '解压与配置文件
  201. Dim InstallTemp As String = PathTemp & "PackInstall\" & RandomInteger(0, 100000) & "\"
  202. Dim InstallLoaders As New List(Of LoaderBase)
  203. Dim OverrideHome As String = If(Json("overrides"), "")
  204. If OverrideHome <> "" Then
  205. InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
  206. Sub(Task As LoaderTask(Of String, Integer))
  207. ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
  208. Task.Progress = 0.6
  209. Dim OverridePath As String = InstallTemp & ArchiveBaseFolder & OverrideHome
  210. '复制结果
  211. If Directory.Exists(OverridePath) Then
  212. CopyDirectory(OverridePath, PathMcFolder & "versions\" & VersionName, Sub(Delta) Task.Progress += Delta * 0.35)
  213. Log($"[ModPack] 整合包 override 复制:{OverridePath} -> {PathMcFolder & "versions\" & VersionName}")
  214. Else
  215. Log($"[ModPack] 整合包中未找到 override 文件夹:{OverridePath}")
  216. End If
  217. Task.Progress = 0.95
  218. '开启版本隔离
  219. WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
  220. End Sub) With {
  221. .ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
  222. End If
  223. '获取 Mod 列表
  224. Dim ModList As New List(Of Integer)
  225. Dim ModOptionalList As New List(Of Integer)
  226. For Each ModEntry In If(Json("files"), {})
  227. If ModEntry("projectID") Is Nothing OrElse ModEntry("fileID") Is Nothing Then
  228. Hint("某项 Mod 缺少必要信息,已跳过:" & ModEntry.ToString)
  229. Continue For
  230. End If
  231. ModList.Add(ModEntry("fileID"))
  232. If ModEntry("required") IsNot Nothing AndAlso Not ModEntry("required").ToObject(Of Boolean) Then ModOptionalList.Add(ModEntry("fileID"))
  233. Next
  234. If ModList.Any Then
  235. Dim ModDownloadLoaders As New List(Of LoaderBase)
  236. '获取 Mod 下载信息
  237. ModDownloadLoaders.Add(New LoaderTask(Of Integer, JArray)("获取 Mod 下载信息",
  238. Sub(Task As LoaderTask(Of Integer, JArray))
  239. '由于 MCIM 缺少下载信息,只使用官方源获取列表
  240. 'TODO: 在 MCIM 源稳定后回调回 DlModRequest
  241. Task.Output = GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(ModList, ",") & "]}", "application/json"))("data")
  242. '如果文件已被删除,则 API 会跳过那一项
  243. If ModList.Count > Task.Output.Count Then Throw New Exception("整合包中的部分 Mod 版本已被 Mod 作者删除,所以没法继续安装了,请向整合包作者反馈该问题")
  244. End Sub) With {.ProgressWeight = ModList.Count / 10}) '每 10 Mod 需要 1s
  245. '构造 NetFile
  246. ModDownloadLoaders.Add(New LoaderTask(Of JArray, List(Of NetFile))("构造 Mod 下载信息",
  247. Sub(Task As LoaderTask(Of JArray, List(Of NetFile)))
  248. Dim FileList As New Dictionary(Of Integer, NetFile)
  249. For Each ModJson In Task.Input
  250. Dim Id As Integer = ModJson("id").ToObject(Of Integer)
  251. '跳过重复的 Mod(疑似 CurseForge Bug)
  252. If FileList.ContainsKey(Id) Then Continue For
  253. '可选 Mod 提示
  254. If ModOptionalList.Contains(Id) Then
  255. If MyMsgBox("是否要下载整合包中的可选文件 " & ModJson("displayName").ToString & "?",
  256. "下载可选文件", "是", "否") = 2 Then
  257. Continue For
  258. End If
  259. End If
  260. '建立 CompFile
  261. Dim File As New CompFile(ModJson, CompType.Mod)
  262. If Not File.Available Then Continue For
  263. '根据 modules 和文件名后缀判断资源类型
  264. Dim TargetFolder As String
  265. If ModJson("modules").Any Then 'modules 可能返回 null(#1006)
  266. Dim ModuleNames = CType(ModJson("modules"), JArray).Select(Function(l) l("name").ToString).ToList
  267. If ModuleNames.Contains("META-INF") OrElse ModuleNames.Contains("mcmod.info") OrElse
  268. File.FileName.EndsWithF(".jar", True) Then
  269. TargetFolder = "mods"
  270. ElseIf ModuleNames.Contains("pack.mcmeta") Then
  271. TargetFolder = "resourcepacks"
  272. Else
  273. TargetFolder = "shaderpacks"
  274. End If
  275. Else
  276. TargetFolder = "mods"
  277. End If
  278. '实际的添加
  279. FileList.Add(Id, File.ToNetFile($"{PathMcFolder}versions\{VersionName}\{TargetFolder}\"))
  280. Task.Progress += 1 / (1 + ModList.Count)
  281. Next
  282. Task.Output = FileList.Values.ToList
  283. End Sub) With {.ProgressWeight = ModList.Count / 200, .Show = False}) '每 200 Mod 需要 1s
  284. '下载 Mod 文件
  285. ModDownloadLoaders.Add(New LoaderDownload("下载 Mod", New List(Of NetFile)) With {.ProgressWeight = ModList.Count * 1.5}) '每个 Mod 需要 1.5s
  286. '构造加载器
  287. InstallLoaders.Add(New LoaderCombo(Of Integer)("下载 Mod(主加载器)", ModDownloadLoaders) With
  288. {.Show = False, .ProgressWeight = ModDownloadLoaders.Sum(Function(l) l.ProgressWeight)})
  289. End If
  290. '构造加载器
  291. Dim Request As New McInstallRequest With {
  292. .TargetVersionName = VersionName,
  293. .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
  294. .MinecraftName = Json("minecraft")("version").ToString,
  295. .ForgeVersion = ForgeVersion,
  296. .NeoForgeVersion = NeoForgeVersion,
  297. .FabricVersion = FabricVersion
  298. }
  299. Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
  300. '构造 Libraries 加载器
  301. Dim LoadersLib As New List(Of LoaderBase)
  302. LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False})
  303. LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
  304. '构造总加载器
  305. Dim Loaders As New List(Of LoaderBase)
  306. Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
  307. Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
  308. Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8})
  309. Loaders.Add(New LoaderTask(Of String, String)("最终整理文件",
  310. Sub(Task As LoaderTask(Of String, String))
  311. '设置图标
  312. Dim VersionFolder As String = PathMcFolder & "versions\" & VersionName & "\"
  313. If Logo IsNot Nothing AndAlso File.Exists(Logo) Then
  314. File.Copy(Logo, VersionFolder & "PCL\Logo.png", True)
  315. WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png")
  316. WriteIni(VersionFolder & "PCL\Setup.ini", "LogoCustom", "True")
  317. Log("[ModPack] 已设置整合包 Logo:" & Logo)
  318. End If
  319. '删除原始整合包文件
  320. For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
  321. If File.Exists(Target) Then
  322. Log("[ModPack] 删除原始整合包文件:" & Target)
  323. File.Delete(Target)
  324. End If
  325. Next
  326. End Sub) With {.ProgressWeight = 0.1, .Show = False})
  327. '重复任务检查
  328. Dim LoaderName As String = "CurseForge 整合包安装:" & VersionName & " "
  329. If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
  330. Hint("该整合包正在安装中!", HintType.Critical)
  331. Throw New CancelledException
  332. End If
  333. '启动
  334. Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
  335. Loader.Start(Request.TargetVersionFolder)
  336. LoaderTaskbarAdd(Loader)
  337. FrmMain.BtnExtraDownload.ShowRefresh()
  338. RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
  339. Return Loader
  340. End Function
  341. 'Modrinth
  342. Private Function InstallPackModrinth(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String, Optional VersionName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String)
  343. '读取 Json 文件
  344. Dim Json As JObject
  345. Try
  346. Json = GetJson(ReadFile(Archive.GetEntry(ArchiveBaseFolder & "modrinth.index.json").Open))
  347. Catch ex As Exception
  348. Throw New Exception("Modrinth 整合包安装信息存在问题", ex)
  349. End Try
  350. If Json("dependencies") Is Nothing OrElse Json("dependencies")("minecraft") Is Nothing Then Throw New Exception("Modrinth 整合包未提供 Minecraft 版本信息")
  351. '获取 Mod API 版本信息
  352. Dim MinecraftVersion As String = Nothing
  353. Dim ForgeVersion As String = Nothing
  354. Dim NeoForgeVersion As String = Nothing
  355. Dim FabricVersion As String = Nothing
  356. For Each Entry As JProperty In If(Json("dependencies"), {})
  357. Select Case Entry.Name.ToLower
  358. Case "minecraft"
  359. MinecraftVersion = Entry.Value.ToString
  360. Case "forge" 'eg. 14.23.5.2859 / 1.19-41.1.0
  361. ForgeVersion = Entry.Value.ToString
  362. Log("[ModPack] 整合包 Forge 版本:" & ForgeVersion)
  363. Case "neoforge", "neo-forge" 'eg. 20.6.98-beta
  364. NeoForgeVersion = Entry.Value.ToString
  365. Log("[ModPack] 整合包 NeoForge 版本:" & NeoForgeVersion)
  366. Case "fabric-loader" 'eg. 0.14.14
  367. FabricVersion = Entry.Value.ToString
  368. Log("[ModPack] 整合包 Fabric 版本:" & FabricVersion)
  369. Case "quilt-loader" 'eg. 1.0.0
  370. Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical)
  371. Throw New CancelledException
  372. Case Else
  373. Hint($"无法安装整合包,其中出现了未知的 Mod 加载器 {Entry.Value}!", HintType.Critical)
  374. Throw New CancelledException
  375. End Select
  376. Next
  377. '获取版本名
  378. If VersionName Is Nothing Then
  379. VersionName = If(Json("name"), "")
  380. Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
  381. If Validate.Validate(VersionName) <> "" Then VersionName = ""
  382. If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
  383. If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
  384. End If
  385. '解压和配置文件
  386. Dim InstallTemp As String = PathTemp & "PackInstall\" & RandomInteger(0, 100000) & "\"
  387. Dim InstallLoaders As New List(Of LoaderBase)
  388. InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
  389. Sub(Task As LoaderTask(Of String, Integer))
  390. ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
  391. Task.Progress = 0.6
  392. '复制 overrides 文件夹和 client-overrides 文件夹
  393. If Directory.Exists(InstallTemp & ArchiveBaseFolder & "overrides") Then
  394. CopyDirectory(InstallTemp & ArchiveBaseFolder & "overrides", PathMcFolder & "versions\" & VersionName,
  395. Sub(Delta) Task.Progress += Delta * 0.25)
  396. Else
  397. Log("[ModPack] 整合包中未找到 override 目录,已跳过")
  398. End If
  399. Task.Progress = 0.85
  400. If Directory.Exists(InstallTemp & ArchiveBaseFolder & "client-overrides") Then
  401. CopyDirectory(InstallTemp & ArchiveBaseFolder & "client-overrides", PathMcFolder & "versions\" & VersionName,
  402. Sub(Delta) Task.Progress += Delta * 0.1)
  403. End If
  404. Task.Progress = 0.95
  405. '开启版本隔离
  406. WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
  407. End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
  408. '获取下载文件列表
  409. Dim FileList As New List(Of NetFile)
  410. For Each File In If(Json("files"), {})
  411. '检查是否需要该文件
  412. If File("env") IsNot Nothing Then
  413. Select Case File("env")("client").ToString
  414. Case "optional"
  415. If MyMsgBox("是否要下载整合包中的可选文件 " & GetFileNameFromPath(File("path").ToString) & "?",
  416. "下载可选文件", "是", "否") = 2 Then
  417. Continue For
  418. End If
  419. Case "unsupported"
  420. Continue For
  421. End Select
  422. End If
  423. '添加下载文件
  424. Dim Urls = File("downloads").Select(Function(t) t.ToString.Replace("://edge.forgecdn", "://media.forgecdn")).ToList
  425. Urls.AddRange(Urls.Select(Function(u) DlSourceModGet(u)).ToList)
  426. Urls = Urls.Distinct.ToList()
  427. FileList.Add(New NetFile(Urls, PathMcFolder & "versions\" & VersionName & "\" & File("path").ToString,
  428. New FileChecker(ActualSize:=File("fileSize").ToObject(Of Long), Hash:=File("hashes")("sha1").ToString), True))
  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 Request As New McInstallRequest With {
  435. .TargetVersionName = VersionName,
  436. .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
  437. .MinecraftName = MinecraftVersion,
  438. .ForgeVersion = ForgeVersion,
  439. .NeoForgeVersion = NeoForgeVersion,
  440. .FabricVersion = FabricVersion
  441. }
  442. Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
  443. '构造 Libraries 加载器
  444. Dim LoadersLib As New List(Of LoaderBase)
  445. LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False})
  446. LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
  447. '构造总加载器
  448. Dim Loaders As New List(Of LoaderBase)
  449. Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
  450. Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
  451. Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8})
  452. Loaders.Add(New LoaderTask(Of String, String)("最终整理文件",
  453. Sub(Task As LoaderTask(Of String, String))
  454. '设置图标
  455. Dim VersionFolder As String = PathMcFolder & "versions\" & VersionName & "\"
  456. If Logo IsNot Nothing AndAlso File.Exists(Logo) Then
  457. File.Copy(Logo, VersionFolder & "PCL\Logo.png", True)
  458. WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png")
  459. WriteIni(VersionFolder & "PCL\Setup.ini", "LogoCustom", "True")
  460. Log("[ModPack] 已设置整合包 Logo:" & Logo)
  461. End If
  462. '删除原始整合包文件
  463. For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
  464. If File.Exists(Target) Then
  465. Log("[ModPack] 删除原始整合包文件:" & Target)
  466. File.Delete(Target)
  467. End If
  468. Next
  469. End Sub) With {.ProgressWeight = 0.1, .Show = False})
  470. '重复任务检查
  471. Dim LoaderName As String = "Modrinth 整合包安装:" & VersionName & " "
  472. If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
  473. Hint("该整合包正在安装中!", HintType.Critical)
  474. Throw New CancelledException
  475. End If
  476. '启动
  477. Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
  478. Loader.Start(Request.TargetVersionFolder)
  479. LoaderTaskbarAdd(Loader)
  480. FrmMain.BtnExtraDownload.ShowRefresh()
  481. RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
  482. Return Loader
  483. End Function
  484. 'HMCL
  485. Private Function InstallPackHMCL(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String) As LoaderCombo(Of String)
  486. '读取 Json 文件
  487. Dim Json As JObject
  488. Try
  489. Json = GetJson(ReadFile(Archive.GetEntry(ArchiveBaseFolder & "modpack.json").Open, Encoding.UTF8))
  490. Catch ex As Exception
  491. Throw New Exception("HMCL 整合包安装信息存在问题", ex)
  492. End Try
  493. '获取版本名
  494. Dim VersionName As String = If(Json("name"), "")
  495. Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
  496. If Validate.Validate(VersionName) <> "" Then VersionName = ""
  497. If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
  498. If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
  499. '解压与配置文件
  500. Dim InstallTemp As String = PathTemp & "PackInstall\" & RandomInteger(0, 100000) & "\"
  501. Dim InstallLoaders As New List(Of LoaderBase)
  502. InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
  503. Sub(Task As LoaderTask(Of String, Integer))
  504. ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
  505. Task.Progress = 0.6
  506. '复制结果
  507. If Directory.Exists(InstallTemp & ArchiveBaseFolder & "minecraft") Then
  508. CopyDirectory(InstallTemp & ArchiveBaseFolder & "minecraft", PathMcFolder & "versions\" & VersionName, Sub(Delta) Task.Progress += Delta * 0.35)
  509. Else
  510. Log("[ModPack] 整合包中未找到 minecraft override 目录,已跳过")
  511. End If
  512. Task.Progress = 0.95
  513. '开启版本隔离
  514. WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
  515. End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
  516. '构造加载器
  517. If Json("gameVersion") Is Nothing Then Throw New Exception("该 HMCL 整合包未提供游戏版本信息,无法安装!")
  518. Dim Request As New McInstallRequest With {
  519. .TargetVersionName = VersionName,
  520. .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
  521. .MinecraftName = Json("gameVersion").ToString
  522. }
  523. Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
  524. '构造 Libraries 加载器(为了使得 Mods 下载结束后再构造,这样才会下载 JumpLoader 文件)
  525. Dim LoadersLib As New List(Of LoaderBase)
  526. LoadersLib.Add(New LoaderTask(Of String, String)("重命名版本 Json(副加载器)",
  527. Sub()
  528. Dim RealFileName As String = PathMcFolder & "versions\" & VersionName & "\" & VersionName & ".json"
  529. Dim OldFileName As String = PathMcFolder & "versions\" & VersionName & "\pack.json"
  530. If File.Exists(OldFileName) Then
  531. '修改 id
  532. Dim FileJson = GetJson(ReadFile(OldFileName))
  533. FileJson("id") = VersionName
  534. '替换文件
  535. File.Delete(OldFileName)
  536. WriteFile(RealFileName, FileJson.ToString)
  537. Log("[ModPack] 已重命名版本 Json:" & RealFileName)
  538. End If
  539. End Sub) With {.ProgressWeight = 0.1, .Show = False})
  540. LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False})
  541. LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
  542. '构造总加载器
  543. Dim Loaders As New List(Of LoaderBase) From {
  544. New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .Block = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)},
  545. New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)},
  546. New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8}
  547. }
  548. '重复任务检查
  549. Dim LoaderName As String = "HMCL 整合包安装:" & VersionName & " "
  550. If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
  551. Hint("该整合包正在安装中!", HintType.Critical)
  552. Throw New CancelledException
  553. End If
  554. '启动
  555. Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
  556. 'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件”
  557. Loader.Start(Request.TargetVersionFolder)
  558. LoaderTaskbarAdd(Loader)
  559. FrmMain.BtnExtraDownload.ShowRefresh()
  560. RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
  561. Return Loader
  562. End Function
  563. 'MMC
  564. Private Function InstallPackMMC(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String) As LoaderCombo(Of String)
  565. '读取 Json 文件
  566. Dim PackJson As JObject, PackInstance As String
  567. Try
  568. PackJson = GetJson(ReadFile(Archive.GetEntry(ArchiveBaseFolder & "mmc-pack.json").Open, Encoding.UTF8))
  569. PackInstance = ReadFile(Archive.GetEntry(ArchiveBaseFolder & "instance.cfg").Open, Encoding.UTF8)
  570. Catch ex As Exception
  571. Throw New Exception("MMC 整合包安装信息存在问题", ex)
  572. End Try
  573. '获取版本名
  574. Dim VersionName As String = If(RegexSeek(PackInstance, "(?<=\nname\=)[^\n]+"), "")
  575. Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
  576. If Validate.Validate(VersionName) <> "" Then VersionName = ""
  577. If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
  578. If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
  579. '解压、配置设置文件
  580. Dim InstallTemp As String = $"{PathTemp}PackInstall\{RandomInteger(0, 100000)}\"
  581. Dim SetupFile As String = $"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini"
  582. Dim InstallLoaders As New List(Of LoaderBase)
  583. InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
  584. Sub(Task As LoaderTask(Of String, Integer))
  585. ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
  586. Task.Progress = 0.6
  587. '复制结果
  588. If Directory.Exists(InstallTemp & ArchiveBaseFolder & ".minecraft") Then
  589. CopyDirectory(InstallTemp & ArchiveBaseFolder & ".minecraft", PathMcFolder & "versions\" & VersionName, Sub(Delta) Task.Progress += Delta * 0.35)
  590. Else
  591. Log("[ModPack] 整合包中未找到 override .minecraft 目录,已跳过")
  592. End If
  593. Task.Progress = 0.95
  594. '开启版本隔离
  595. WriteIni(SetupFile, "VersionArgumentIndie", 1)
  596. '读取 MMC 设置文件(#2655)
  597. Try
  598. Dim MMCSetupFile As String = InstallTemp & ArchiveBaseFolder & "instance.cfg"
  599. If File.Exists(MMCSetupFile) Then
  600. '将其中的等号替换为冒号,以符合 ini 文件格式
  601. WriteFile(MMCSetupFile, ReadFile(MMCSetupFile).Replace("=", ":"))
  602. If ReadIni(MMCSetupFile, "OverrideCommands", False) Then
  603. Dim PreLaunchCommand As String = ReadIni(MMCSetupFile, "PreLaunchCommand")
  604. If PreLaunchCommand <> "" Then
  605. PreLaunchCommand = PreLaunchCommand.Replace("\""", """").
  606. Replace("$INST_JAVA", "{java}javaw.exe").
  607. Replace("$INST_MC_DIR\", "{minecraft}").Replace("$INST_MC_DIR", "{minecraft}").
  608. Replace("$INST_DIR\", "{verpath}").Replace("$INST_DIR", "{verpath}").
  609. Replace("$INST_ID", "{name}").Replace("$INST_NAME", "{name}")
  610. WriteIni(SetupFile, "VersionAdvanceRun", PreLaunchCommand)
  611. Log("[ModPack] 迁移 MultiMC 版本独立设置:启动前执行命令:" & PreLaunchCommand)
  612. End If
  613. End If
  614. If ReadIni(MMCSetupFile, "JoinServerOnLaunch", False) Then
  615. Dim ServerAddress As String = ReadIni(MMCSetupFile, "JoinServerOnLaunchAddress").Replace("\""", """")
  616. WriteIni(SetupFile, "VersionServerEnter", ServerAddress)
  617. Log("[ModPack] 迁移 MultiMC 版本独立设置:自动进入服务器:" & ServerAddress)
  618. End If
  619. If ReadIni(MMCSetupFile, "IgnoreJavaCompatibility", False) Then
  620. WriteIni(SetupFile, "VersionAdvanceJava", True)
  621. Log("[ModPack] 迁移 MultiMC 版本独立设置:忽略 Java 兼容性警告")
  622. End If
  623. Dim Logo As String = ReadIni(MMCSetupFile, "iconKey", "")
  624. If Logo <> "" AndAlso File.Exists($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png") Then
  625. WriteIni(SetupFile, "LogoCustom", True)
  626. WriteIni(SetupFile, "Logo", "PCL\Logo.png")
  627. CopyFile($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png", $"{PathMcFolder}versions\{VersionName}\PCL\Logo.png")
  628. Log($"[ModPack] 迁移 MultiMC 版本独立设置:版本图标({Logo}.png)")
  629. End If
  630. End If
  631. Catch ex As Exception
  632. Log(ex, $"读取 MMC 配置文件失败({InstallTemp}{ArchiveBaseFolder}instance.cfg)")
  633. End Try
  634. End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
  635. '构造版本安装请求
  636. If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!")
  637. Dim Request As New McInstallRequest With {.TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\"}
  638. For Each Component In PackJson("components")
  639. Select Case If(Component("uid"), "").ToString
  640. Case "org.lwjgl"
  641. Log("[ModPack] 已跳过 LWJGL 项")
  642. Case "net.minecraft"
  643. Request.MinecraftName = Component("version")
  644. Case "net.minecraftforge"
  645. Request.ForgeVersion = Component("version")
  646. Case "net.neoforged"
  647. Request.NeoForgeVersion = Component("version")
  648. Case "net.fabricmc.fabric-loader"
  649. Request.FabricVersion = Component("version")
  650. Case "org.quiltmc.quilt-loader" 'eg. 1.0.0
  651. Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical)
  652. Throw New CancelledException
  653. End Select
  654. Next
  655. '构造加载器
  656. Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
  657. '构造 Libraries 加载器
  658. Dim LoadersLib As New List(Of LoaderBase)
  659. LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False})
  660. LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
  661. '构造总加载器
  662. Dim Loaders As New List(Of LoaderBase)
  663. Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .Block = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
  664. Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
  665. Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8})
  666. '重复任务检查
  667. Dim LoaderName As String = "MMC 整合包安装:" & VersionName & " "
  668. If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
  669. Hint("该整合包正在安装中!", HintType.Critical)
  670. Throw New CancelledException
  671. End If
  672. '启动
  673. Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
  674. Loader.Start(Request.TargetVersionFolder)
  675. LoaderTaskbarAdd(Loader)
  676. FrmMain.BtnExtraDownload.ShowRefresh()
  677. RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
  678. Return Loader
  679. End Function
  680. 'MCBBS
  681. Private Function InstallPackMCBBS(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String,
  682. Optional VersionName As String = Nothing) As LoaderCombo(Of String)
  683. '读取 Json 文件
  684. Dim Json As JObject
  685. Try
  686. Dim Entry = If(Archive.GetEntry(ArchiveBaseFolder & "mcbbs.packmeta"), Archive.GetEntry(ArchiveBaseFolder & "manifest.json"))
  687. Json = GetJson(ReadFile(Entry.Open, Encoding.UTF8))
  688. Catch ex As Exception
  689. Throw New Exception("MCBBS 整合包安装信息存在问题", ex)
  690. End Try
  691. '获取版本名
  692. If VersionName Is Nothing Then
  693. VersionName = If(Json("name"), "")
  694. Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
  695. If Validate.Validate(VersionName) <> "" Then VersionName = ""
  696. If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
  697. If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
  698. End If
  699. '解压与配置文件
  700. Dim InstallTemp As String = PathTemp & "PackInstall\" & RandomInteger(0, 100000) & "\"
  701. Dim InstallLoaders As New List(Of LoaderBase)
  702. InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
  703. Sub(Task As LoaderTask(Of String, Integer))
  704. ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
  705. Task.Progress = 0.6
  706. '复制结果
  707. If Directory.Exists(InstallTemp & ArchiveBaseFolder & "overrides") Then
  708. CopyDirectory(InstallTemp & ArchiveBaseFolder & "overrides", PathMcFolder & "versions\" & VersionName,
  709. Sub(Delta) Task.Progress += 0.35 * Delta)
  710. Else
  711. Log("[ModPack] 整合包中未找到 overrides 目录,已跳过")
  712. End If
  713. Task.Progress = 0.95
  714. '开启版本隔离
  715. WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
  716. End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
  717. '构造加载器
  718. If Json("addons") Is Nothing Then Throw New Exception("该 MCBBS 整合包未提供游戏版本附加信息,无法安装!")
  719. Dim Addons As New Dictionary(Of String, String)
  720. For Each Entry In Json("addons")
  721. Addons.Add(Entry("id"), Entry("version"))
  722. Next
  723. If Not Addons.ContainsKey("game") Then Throw New Exception("该 MCBBS 整合包未提供游戏版本信息,无法安装!")
  724. If Addons.ContainsKey("quilt") Then
  725. Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical)
  726. Throw New CancelledException
  727. End If
  728. Dim Request As New McInstallRequest With {
  729. .TargetVersionName = VersionName,
  730. .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
  731. .MinecraftName = Addons("game"),
  732. .OptiFineVersion = If(Addons.ContainsKey("optifine"), Addons("optifine"), Nothing),
  733. .ForgeVersion = If(Addons.ContainsKey("forge"), Addons("forge"), Nothing),
  734. .NeoForgeVersion = If(Addons.ContainsKey("neoforge"), Addons("neoforge"), Nothing),
  735. .FabricVersion = If(Addons.ContainsKey("fabric"), Addons("fabric"), Nothing)
  736. }
  737. Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
  738. '构造 Libraries 加载器
  739. Dim LoadersLib As New List(Of LoaderBase)
  740. LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False})
  741. LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
  742. '构造总加载器
  743. Dim Loaders As New List(Of LoaderBase)
  744. Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .Block = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
  745. Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
  746. Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8})
  747. '重复任务检查
  748. Dim LoaderName As String = "MCBBS 整合包安装:" & VersionName & " "
  749. If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
  750. Hint("该整合包正在安装中!", HintType.Critical)
  751. Throw New CancelledException
  752. End If
  753. '启动
  754. Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
  755. 'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件”
  756. Loader.Start(Request.TargetVersionFolder)
  757. LoaderTaskbarAdd(Loader)
  758. FrmMain.BtnExtraDownload.ShowRefresh()
  759. RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
  760. Return Loader
  761. End Function
  762. '带启动器的压缩包
  763. Private Function InstallPackLauncherPack(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String) As LoaderCombo(Of String)
  764. '获取解压路径
  765. MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True)
  766. Dim TargetFolder As String = SelectFolder("选择安装目标(必须是一个空文件夹)")
  767. If String.IsNullOrEmpty(TargetFolder) Then Throw New CancelledException
  768. If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Critical) : Throw New CancelledException
  769. If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Critical) : Throw New CancelledException
  770. '解压
  771. Dim Loader As New LoaderCombo(Of String)("解压压缩包", {
  772. New LoaderTask(Of String, Integer)("解压压缩包",
  773. Sub(Task As LoaderTask(Of String, Integer))
  774. ExtractModpackFiles(TargetFolder, FileAddress, Task, 0.9)
  775. Thread.Sleep(400) '避免文件争用
  776. '查找解压后的 exe 文件
  777. Dim Launcher As String = Nothing
  778. For Each ExeFile In Directory.GetFiles(TargetFolder, "*.exe", SearchOption.AllDirectories)
  779. Dim Info = FileVersionInfo.GetVersionInfo(ExeFile)
  780. Log($"[Modpack] 文件 {ExeFile} 的产品名标识为 {Info.ProductName}")
  781. If Info.ProductName = "Plain Craft Launcher" Then
  782. Launcher = ExeFile
  783. ElseIf (Info.ProductName.ContainsF("Launcher", True) OrElse Info.ProductName.ContainsF("启动器", True)) AndAlso
  784. Not Info.ProductName = "Plain Craft Launcher Admin Manager" Then
  785. If Launcher Is Nothing Then Launcher = ExeFile
  786. End If
  787. Next
  788. Task.Progress = 0.95
  789. '尝试使用附带的启动器打开
  790. If Launcher IsNot Nothing Then
  791. Log("[Modpack] 找到压缩包中附带的启动器:" & Launcher)
  792. If MyMsgBox($"整合包中似乎自带了启动器,是否换用它继续安装?{vbCrLf}通常推荐这样做,以获得最佳体验。{vbCrLf}即将打开:{Launcher}", "换用整合包启动器?", "继续", "取消") = 1 Then
  793. ShellOnly(Launcher, "--wait")
  794. Log("[Modpack] 为换用整合包中的启动器启动,强制结束程序")
  795. FrmMain.EndProgram(False)
  796. Return
  797. End If
  798. Else
  799. Log("[Modpack] 未找到压缩包中附带的启动器")
  800. End If
  801. '加入文件夹列表
  802. Dim VersionName As String = GetFolderNameFromPath(TargetFolder)
  803. PageSelectLeft.AddFolder(
  804. TargetFolder & ArchiveBaseFolder.Replace("/", "\").TrimStart("\"), '格式例如:包裹文件夹\.minecraft\(最短为空字符串)
  805. VersionName, False)
  806. '调用 modpack 文件进行安装
  807. Dim ModpackFile = Directory.GetFiles(TargetFolder, "modpack.*", SearchOption.AllDirectories).First
  808. Log("[Modpack] 调用 modpack 文件继续安装:" & ModpackFile)
  809. ModpackInstall(ModpackFile, VersionName)
  810. End Sub)
  811. })
  812. Loader.Start(TargetFolder)
  813. LoaderTaskbarAdd(Loader)
  814. FrmMain.BtnExtraDownload.ShowRefresh()
  815. FrmMain.BtnExtraDownload.Ribble()
  816. Return Loader
  817. End Function
  818. '普通压缩包
  819. Private Function InstallPackCompress(FileAddress As String, Archive As Compression.ZipArchive) As LoaderCombo(Of String)
  820. '尝试定位 .minecraft 文件夹:寻找形如 “/versions/XXX/XXX.json” 的路径
  821. Dim Match As RegularExpressions.Match = Nothing
  822. Dim Regex As New RegularExpressions.Regex("^.*\/(?=versions\/(?<ver>[^\/]+)\/(\k<ver>)\.json$)", RegularExpressions.RegexOptions.IgnoreCase)
  823. For Each Entry In Archive.Entries
  824. Dim EntryMatch = Regex.Match("/" & Entry.FullName)
  825. If EntryMatch.Success Then
  826. Match = EntryMatch
  827. Exit For
  828. End If
  829. Next
  830. If Match Is Nothing Then Throw New Exception("未能找到适合的文件结构,这可能不是一个 MC 压缩包") '没有匹配
  831. Dim ArchiveBaseFolder As String = Match.Value.Replace("/", "\").TrimStart("\") '格式例如:包裹文件夹\.minecraft\(最短为空字符串)
  832. Dim VersionName As String = Match.Groups(1).Value
  833. Log("[ModPack] 检测到压缩包的 .minecraft 根目录:" & ArchiveBaseFolder & ",命中的版本名:" & VersionName)
  834. '获取解压路径
  835. MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True)
  836. Dim TargetFolder As String = SelectFolder("选择安装目标(必须是一个空文件夹)")
  837. If String.IsNullOrEmpty(TargetFolder) Then Throw New CancelledException
  838. If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Critical) : Throw New CancelledException
  839. If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Critical) : Throw New CancelledException
  840. '解压
  841. Dim Loader As New LoaderCombo(Of String)("解压压缩包", {
  842. New LoaderTask(Of String, Integer)("解压压缩包",
  843. Sub(Task As LoaderTask(Of String, Integer))
  844. ExtractModpackFiles(TargetFolder, FileAddress, Task, 0.95)
  845. '加入文件夹列表
  846. PageSelectLeft.AddFolder(TargetFolder & ArchiveBaseFolder, GetFolderNameFromPath(TargetFolder), False)
  847. Thread.Sleep(400) '避免文件争用
  848. RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.VersionSelect))
  849. End Sub)
  850. }) With {.OnStateChanged = AddressOf McInstallState}
  851. Loader.Start(TargetFolder)
  852. LoaderTaskbarAdd(Loader)
  853. FrmMain.BtnExtraDownload.ShowRefresh()
  854. FrmMain.BtnExtraDownload.Ribble()
  855. Return Loader
  856. End Function
  857. #End Region
  858. #End Region
  859. #Region "导出"
  860. Private ExpTempDir As String = PathTemp & "PackExport\"
  861. ''' <summary>
  862. ''' 整合包类型。
  863. ''' </summary>
  864. Public Enum ModpackType
  865. Modrinth
  866. End Enum
  867. ''' <summary>
  868. ''' 导出整合包的选项。
  869. ''' </summary>
  870. Public Class ExportOptions
  871. ''' <summary>
  872. ''' 要导出的版本。
  873. ''' </summary>
  874. Public Version As McVersion
  875. ''' <summary>
  876. ''' 要保存整合包的位置。
  877. ''' </summary>
  878. Public Dest As String
  879. ''' <summary>
  880. ''' 是否包括 PCL 文件。
  881. ''' </summary>
  882. Public IncludePCL As Boolean
  883. ''' <summary>
  884. ''' 整合包类型。
  885. ''' </summary>
  886. Public Type As ModpackType
  887. ''' <summary>
  888. ''' 要保留到整合包的文件。
  889. ''' </summary>
  890. Public Additional As String()
  891. ''' <summary>
  892. ''' 是否需要保留 PCL 全局设置。仅在包括 PCL 时有效。
  893. ''' </summary>
  894. Public PCLSetupGlobal As Boolean = True
  895. ''' <summary>
  896. ''' 整合包名称。
  897. ''' </summary>
  898. Public Name As String = ""
  899. ''' <summary>
  900. ''' 整合包描述。
  901. ''' </summary>
  902. Public Desc As String = ""
  903. ''' <summary>
  904. ''' 版本号。
  905. ''' </summary>
  906. Public VerID As String = ""
  907. Public Sub New(Version As McVersion,
  908. Dest As String,
  909. IncludePCL As Boolean,
  910. Additional As String(),
  911. Optional PCLSetupGlobal As Boolean = True,
  912. Optional Name As String = "",
  913. Optional Desc As String = "",
  914. Optional VerID As String = "")
  915. Me.Version = Version
  916. Me.Dest = Dest
  917. Me.IncludePCL = IncludePCL
  918. Me.Additional = Additional
  919. Me.PCLSetupGlobal = PCLSetupGlobal
  920. Me.Name = Name
  921. Me.Desc = Desc
  922. Me.VerID = VerID
  923. End Sub
  924. End Class
  925. ''' <summary>
  926. ''' 导出整合包,返回是否成功。
  927. ''' </summary>
  928. Public Function ModpackExport(Task As LoaderTask(Of ExportOptions, Boolean)) As Boolean
  929. If Task.Input.IncludePCL Then Return ExportCompressed(Task)
  930. Return ExportModrinth(Task)
  931. End Function
  932. #Region "冗余"
  933. Private VersionRedundant As String() = {"screenshots", "backups", "command_history\.txt", '个人文件
  934. ".*-natives", "server-resource-packs", "user.*cache\.json", "\.optifine", "\.fabric", "\.mixin\.out", '缓存
  935. ".*\.jar", "downloads", "realms_persistence.json", "\$\{natives_directory\}", "essential", '可联网更新
  936. "logs", "crash-reports", ".*\.log", "debug", '日志
  937. ".*\.dat_old", ".*\.old", '备份
  938. "\$\{quickPlayPath\}", '服务器
  939. "\.replay_cache", "replay_recordings", "replay_videos", 'ReplayMod
  940. "irisUpdateInfo\.json", 'Iris
  941. "modernfix", 'ModernFix 模组
  942. "modtranslations", 'Mod 翻译
  943. "schematics", 'schematics 模组
  944. ".*\.BakaCoreInfo", 'BakaXL 配置
  945. "hmclversion\.cfg", "log4j2\.xml", 'HMCL 配置
  946. "assets", "libraries", "\$natives", "launcher_profiles\.json", "versions" '开启版本隔离时排除的文件
  947. }
  948. Public Function IsVerRedundant(FilePath As String) As Boolean
  949. FilePath = FilePath.Replace("/", "\")
  950. For Each regex In VersionRedundant
  951. If RegexCheck(GetFileNameFromPath(FilePath), regex, RegularExpressions.RegexOptions.IgnoreCase) Then Return True
  952. If FilePath.EndsWithF("\journeymap\data\") OrElse
  953. FilePath.EndsWithF("\journeymap\data") OrElse
  954. FilePath.EndsWithF("\mods\.connector") Then
  955. Return True
  956. End If
  957. Next
  958. Return False
  959. End Function
  960. Private MustExport As String() = {
  961. "mods", "PCL", ".*\.json"
  962. }
  963. Public Function IsMustExport(FilePath As String) As Boolean
  964. For Each regex In MustExport
  965. If RegexCheck(GetFileNameFromPath(FilePath), regex, RegularExpressions.RegexOptions.IgnoreCase) Then Return True
  966. Next
  967. Return False
  968. End Function
  969. #End Region
  970. #Region "不同类型整合包的导出方法"
  971. Private Function ExportModrinth(Task As LoaderTask(Of ExportOptions, Boolean)) As Boolean
  972. Try
  973. Dim Version = Task.Input.Version
  974. Dim DestPath = Task.Input.Dest
  975. Dim Additional = Task.Input.Additional
  976. Dim Name = Task.Input.Name
  977. Dim VerID = Task.Input.VerID
  978. Log($"[Export] 导出整合包(Modrinth):{Version.Path} -> {DestPath},额外版本文件 {Additional.Count} 个")
  979. Dim tempDir As String = $"{ExpTempDir}{GetUuid()}\"
  980. Log($"[Export] 缓存文件夹:{tempDir}")
  981. Directory.CreateDirectory(tempDir)
  982. #Region "0.04-0.40:从 Modrinth 获取 Mod 工程信息,得到 URL"
  983. Dim Mods As New Dictionary(Of String, McMod)
  984. If Directory.Exists(Version.Path & "mods\") Then
  985. For Each m In Directory.EnumerateFiles(Version.Path & "mods\")
  986. If m.EndsWithF(".jar") Then Mods.Add(GetFileNameFromPath(m), New McMod(m))
  987. Next
  988. End If
  989. '从 Modrinth 获取信息
  990. Dim ModrinthMapping As New Dictionary(Of String, JObject) 'Modrinth 获取到的 Mod
  991. Dim CurseForgeMapping As New Dictionary(Of String, JObject) 'CurseForge 获取到的 Mod
  992. Dim Failed = Mods.ToArray().ToDictionary(Function(a) a.Key, Function(a) a.Value) '获取失败的 Mod
  993. Dim ModrinthHashes = Mods.Select(Function(m) m.Value.ModrinthHash).ToList()
  994. If Mods.Count = 0 Then GoTo JumpMod
  995. Dim ModrinthRaw = CType(GetJson(NetRequestRetry("https://api.modrinth.com/v2/version_files", "POST",
  996. $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")), JObject)
  997. Log($"[Export] 从 Modrinth 获取到 {ModrinthRaw.Count} 个本地 Mod 的对应信息")
  998. Task.Progress = 0.35
  999. For Each Entry In Mods
  1000. If (Not ModrinthRaw.ContainsKey(Entry.Value.ModrinthHash)) OrElse
  1001. (ModrinthRaw(Entry.Value.ModrinthHash)("files")(0)("hashes")("sha1") <> Entry.Value.ModrinthHash) Then
  1002. Continue For
  1003. End If
  1004. Failed.Remove(Entry.Key)
  1005. ModrinthMapping.Add(Entry.Key, ModrinthRaw(Entry.Value.ModrinthHash)("files")(0))
  1006. Next
  1007. Task.Progress = 0.4
  1008. #End Region
  1009. #Region "0.40-0.80:从 CurseForge 继续获取"
  1010. Dim CurseForgeHashes = Mods.Select(Function(m) m.Value.CurseForgeHash).ToList()
  1011. Dim CurseForgeRaw = CType(CType(GetJson(NetRequestRetry("https://api.curseforge.com/v1/fingerprints/432/", "POST",
  1012. $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer)
  1013. Log($"[Export] 从 CurseForge 获取到 {CurseForgeRaw.Count} 个本地 Mod 的对应信息")
  1014. Task.Progress = 0.7
  1015. For Each m In CurseForgeRaw
  1016. Dim hash As String = ""
  1017. For Each h In m("file")("hashes") '获取 Modrinth Hash
  1018. If h("algo").ToString = 1 Then
  1019. hash = h("value")
  1020. Exit For
  1021. End If
  1022. Next
  1023. If String.IsNullOrEmpty(hash) OrElse String.IsNullOrEmpty(m("file")("downloadUrl")) Then Continue For
  1024. m("file")("ModrinthHash") = hash
  1025. Dim f = Mods.ToArray()
  1026. For Each file In f
  1027. If file.Value.ModrinthHash = hash Then
  1028. CurseForgeMapping.Add(file.Key, m("file"))
  1029. Failed.Remove(file.Key)
  1030. Exit For
  1031. End If
  1032. Next
  1033. Next
  1034. Task.Progress = 0.8
  1035. #End Region
  1036. JumpMod:
  1037. #Region "0.80-0.90:合并下载链接,写入 Json 文件"
  1038. '获取作为检查目标的加载器和版本
  1039. Dim ModLoaders = GetTargetModLoaders()
  1040. Dim McVersion = Version.Version.McName
  1041. Log($"[Export] 目标加载器:{ModLoaders.Join("/")},版本:{McVersion}")
  1042. '合并下载链接
  1043. Dim BothUrls As New Dictionary(Of String, String()) '两个下载源都有 URL 的 Mod
  1044. For Each modrinth In ModrinthMapping
  1045. Dim curseforge As New JObject
  1046. If CurseForgeMapping.TryGetValue(modrinth.Key, curseforge) Then
  1047. BothUrls.Add(modrinth.Key, {modrinth.Value("url"), curseforge("downloadUrl")})
  1048. End If
  1049. Next
  1050. Task.Progress = 0.83
  1051. Dim files As New JArray
  1052. For Each m In ModrinthMapping
  1053. files.Add(New JObject From {
  1054. {"path", $"mods/{m.Key}"},
  1055. {"hashes", m.Value("hashes")},
  1056. {"env", New JObject From {{"client", "required"}, {"server", "required"}}},
  1057. {"downloads", If(BothUrls.ContainsKey(m.Key), JArray.FromObject(BothUrls(m.Key)), New JArray From {m.Value("url")})},
  1058. {"fileSize", m.Value("size")}
  1059. })
  1060. Next
  1061. For Each m In CurseForgeMapping
  1062. If Not BothUrls.ContainsKey(m.Key) Then
  1063. files.Add(New JObject From {
  1064. {"path", $"mods/{m.Key}"},
  1065. {"hashes", New JObject From {{"sha1", m.Value("ModrinthHash").ToString}}},
  1066. {"env", New JObject From {{"client", "required"}, {"server", "required"}}},
  1067. {"downloads", New JArray From {m.Value("downloadUrl")}},
  1068. {"fileSize", m.Value("fileLength")}
  1069. })
  1070. End If
  1071. Next
  1072. Task.Progress = 0.87
  1073. Dim depend As New JObject From {{"minecraft", McVersion}}
  1074. If Version.Version.HasForge Then depend.Add("forge", Version.Version.ForgeVersion)
  1075. If Version.Version.HasFabric Then depend.Add("fabric-loader", Version.Version.FabricVersion)
  1076. If Version.Version.HasNeoForge Then depend.Add("neoforge", Version.Version.NeoForgeVersion)
  1077. Dim json As New JObject From {
  1078. {"game", "minecraft"},
  1079. {"formatVersion", 1},
  1080. {"versionId", VerID},
  1081. {"name", If(String.IsNullOrEmpty(Name), Version.Name, Name)},
  1082. {"summary", Version.Info},
  1083. {"files", files},
  1084. {"dependencies", depend}
  1085. }
  1086. File.WriteAllText(tempDir & "modrinth.index.json", json.ToString)
  1087. Task.Progress = 0.9
  1088. #End Region
  1089. #Region "0.90-0.99:将其余文件保存到 overrides 目录"
  1090. For Each m In Failed
  1091. CopyFile(m.Value.Path, tempDir & "overrides\mods\" & m.Value.FileName)
  1092. Next
  1093. Task.Progress = 0.92
  1094. '额外文件
  1095. For Each p In Additional
  1096. If String.IsNullOrWhiteSpace(p) Then Continue For '传空字串进去会直接把整个版本文件夹拷过去
  1097. If Not p.StartsWithF(GetPathFromFullPath(GetPathFromFullPath(Version.Path)), IgnoreCase:=True) Then Continue For
  1098. Dim relative As String = p.Replace(Version.Path, "").Replace(GetPathFromFullPath(GetPathFromFullPath(Version.Path)), "")
  1099. If File.Exists(p) AndAlso Not IsVerRedundant(p) Then
  1100. CopyFile(p, $"{tempDir}overrides\{relative}")
  1101. End If
  1102. Next
  1103. Task.Progress = 0.99
  1104. #End Region
  1105. If File.Exists(DestPath) Then File.Delete(DestPath) '选择文件的时候已经确认了要替换
  1106. ZipFile.CreateFromDirectory(tempDir, DestPath)
  1107. DeleteDirectory(tempDir)
  1108. Return True
  1109. Catch ex As Exception
  1110. Log(ex, "导出整合包失败", LogLevel.Msgbox)
  1111. Return False
  1112. End Try
  1113. End Function
  1114. Private Function ExportCompressed(Task As LoaderTask(Of ExportOptions, Boolean)) As Boolean
  1115. Try
  1116. Dim Version = Task.Input.Version
  1117. Dim DestPath = Task.Input.Dest
  1118. Dim Additional = Task.Input.Additional
  1119. Dim Name = Task.Input.Name
  1120. Dim VerID = Task.Input.VerID
  1121. Dim PCLSetupGlobal = Task.Input.PCLSetupGlobal
  1122. Log($"[Export] 导出整合包(含启动器):{Version.Path} -> {DestPath},额外版本文件 {Additional.Count} 个,全局设置 {If(PCLSetupGlobal, "导出", "不导出")}")
  1123. Dim tempDir As String = $"{ExpTempDir}{GetUuid()}\"
  1124. Log($"[Export] 最终压缩包的缓存文件夹:{tempDir}")
  1125. Directory.CreateDirectory(tempDir)
  1126. Task.Progress = 0.04
  1127. Log("[Export] 开始导出 Modrinth 整合包")
  1128. Task.Input.Dest = $"{tempDir}modpack.mrpack"
  1129. ExportModrinth(Task)
  1130. Log($"[Export] 正在复制 PCL 本体")
  1131. CopyFile(PathWithName, tempDir & GetFileNameFromPath(PathWithName))
  1132. If PCLSetupGlobal Then
  1133. Log($"[Export] 正在复制 PCL 全局配置")
  1134. CopyFile(Path & "PCL\Setup.ini", tempDir & "PCL\Setup.ini")
  1135. End If
  1136. If File.Exists(DestPath) Then File.Delete(DestPath) '选择文件的时候已经确认了要替换
  1137. ZipFile.CreateFromDirectory(tempDir, DestPath)
  1138. DeleteDirectory(tempDir)
  1139. Return True
  1140. Catch ex As Exception
  1141. Log(ex, "导出整合包失败", LogLevel.Msgbox)
  1142. Return False
  1143. End Try
  1144. End Function
  1145. #End Region
  1146. #End Region
  1147. End Module