using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Text; using System.Text.RegularExpressions; using PCL.Core.App; using PCL.Core.App.Localization; using PCL.Core.UI; using PCL.Core.Utils.Validate; using PCL.Network; using PCL.Network.Loaders; using static PCL.ModLoader; using PCL.Core.Utils; namespace PCL; public static class ModModpack { // 触发整合包安装的外部接口 /// /// 弹窗要求选择一个整合包文件并进行安装。 /// public static void ModpackInstall() { var file = SystemDialogs.SelectFile(Lang.Text("Minecraft.Download.Modpack.FileDialog.Filter"), Lang.Text("Minecraft.Download.Modpack.FileDialog.Title")); // 选择整合包文件 if (string.IsNullOrEmpty(file)) return; ModBase.RunInThread(() => { try { ModpackInstall(file); } catch (ModBase.CancelledException ex) { } catch (Exception ex) { ModBase.Log( ex, "手动安装整合包失败", ModBase.LogLevel.Msgbox, userSummary: Lang.Text("Minecraft.Download.Modpack.Error.OperationFailed")); } }); } /// /// 构建并启动安装给定的整合包文件的加载器,并返回该加载器。若失败则抛出异常。 /// 必须在工作线程执行。 /// /// public static LoaderCombo ModpackInstall(string file, string instanceName = null, string logo = null, string resourceId = null, bool isOnlineInstall = false) { ModBase.Log("[ModPack] 整合包安装请求:" + (file ?? "null")); ZipArchive archive = null; var archiveBaseFolder = ""; try { // 字符校验 var targetFolder = $@"{ModFolder.mcFolderSelected}versions\{instanceName}\"; if (targetFolder.Contains("!") || targetFolder.Contains(";")) { HintService.Hint(Lang.Text("Minecraft.Download.Modpack.InvalidGamePathChars", targetFolder), HintType.Error); throw new ModBase.CancelledException(); } // 获取整合包种类与关键 Json var packType = -1; do { try { archive = new ZipArchive(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)); if (archive.Entries.Any(e => e.IsEncrypted)) throw new Exception(Lang.Text("Minecraft.Download.Modpack.EncryptedArchiveUnsupported")); // 从根目录判断整合包类型 if (archive.GetEntry("mcbbs.packmeta") is not null) { packType = 3; break; } // MCBBS 整合包(优先于 manifest.json 判断) if (archive.GetEntry("mmc-pack.json") is not null) { packType = 2; break; } // MMC 整合包(优先于 manifest.json 判断,#4194) if (archive.GetEntry("modrinth.index.json") is not null) { packType = 4; break; } // Modrinth 整合包 if (archive.GetEntry("manifest.json") is not null) { var json = (JsonObject)ModBase.GetJson(ModBase.ReadFile(archive.GetEntry("manifest.json").Open(), Encoding.UTF8)); if (json["addons"] is null) { packType = 0; break; // CurseForge 整合包 } packType = 3; break; // MCBBS 整合包 } if (archive.GetEntry("modpack.json") is not null) { packType = 1; break; } // HMCL 整合包 if (archive.GetEntry("modpack.zip") is not null || archive.GetEntry("modpack.mrpack") is not null) { packType = 9; break; } // 带启动器的压缩包 // 从一级目录判断整合包类型 var exitTry = false; foreach (var Entry in archive.Entries) { var fullNames = Entry.FullName.Split("/"); archiveBaseFolder = fullNames[0] + "/"; // 确定为一级目录下 if (fullNames.Count() != 2) continue; // 判断是否为关键文件 if (fullNames[1] == "mcbbs.packmeta") { packType = 3; exitTry = true; break; } // MCBBS 整合包(优先于 manifest.json 判断) if (fullNames[1] == "mmc-pack.json") { packType = 2; exitTry = true; break; } // MMC 整合包(优先于 manifest.json 判断,#4194) if (fullNames[1] == "modrinth.index.json") { packType = 4; exitTry = true; break; } // Modrinth 整合包 if (fullNames[1] == "manifest.json") { var json = (JsonObject)ModBase.GetJson(ModBase.ReadFile(Entry.Open(), Encoding.UTF8)); if (json["addons"] is null) { packType = 0; exitTry = true; break; // CurseForge 整合包 } packType = 3; archiveBaseFolder = "overrides/"; exitTry = true; break; // MCBBS 整合包 } if (fullNames[1] == "modpack.json") { packType = 1; exitTry = true; break; } // HMCL 整合包 if (fullNames[1] == "modpack.zip" || fullNames[1] == "modpack.mrpack") { packType = 9; exitTry = true; break; } // 带启动器的压缩包 } if (exitTry) break; } catch (Exception ex) { if (ex.Message.Contains("Error.WinIOError")) throw new Exception(Lang.Text("Minecraft.Download.Modpack.OpenFailed"), ex); else if (file.EndsWithF(".rar", true)) throw new Exception(Lang.Text("Minecraft.Download.Modpack.RarUnsupported"), ex); else throw new Exception(Lang.Text("Minecraft.Download.Modpack.UnsupportedArchive"), ex); } } while (false); // 执行对应的安装方法 switch (packType) { case 0: { ModBase.Log("[ModPack] 整合包种类:CurseForge"); return InstallPackCurseForge(file, archive, archiveBaseFolder, instanceName, logo, resourceId, isOnlineInstall); } case 1: { ModBase.Log("[ModPack] 整合包种类:HMCL"); return InstallPackHMCL(file, archive, archiveBaseFolder); } case 2: { ModBase.Log("[ModPack] 整合包种类:MMC"); return InstallPackMMC(file, archive, archiveBaseFolder); } case 3: { ModBase.Log("[ModPack] 整合包种类:MCBBS"); return InstallPackMCBBS(file, archive, archiveBaseFolder, instanceName); } case 4: { ModBase.Log("[ModPack] 整合包种类:Modrinth"); return InstallPackModrinth(file, archive, archiveBaseFolder, instanceName, logo, resourceId, isOnlineInstall); } case 9: { ModBase.Log("[ModPack] 整合包种类:带启动器的压缩包"); return InstallPackLauncherPack(file, archive, archiveBaseFolder); } default: { ModBase.Log("[ModPack] 整合包种类:未能识别,假定为压缩包"); return InstallPackCompress(file, archive); } } } finally { if (archive is not null) archive.Dispose(); } } private static void ExtractModpackFiles(string installTemp, string fileAddress, LoaderBase loader, double progressIncrement) { // 解压文件 var retryCount = 1; var encode = Encoding.GetEncoding("GB18030"); var initialProgress = loader.Progress; while (retryCount <= 5) try { loader.Progress = initialProgress; // 删除旧目录 ModBase.DeleteDirectory(installTemp); // 解压文件,ProgressIncrementHandler 通过 Lambda 更新进度 ModBase.ExtractFile(fileAddress, installTemp, encode, delta => loader.Progress += delta * progressIncrement); // 解压成功,更新进度并退出循环 loader.Progress = initialProgress + progressIncrement; return; } catch (Exception ex) { ModBase.Log(ex, $"第 {retryCount} 次解压尝试失败"); if (ex is ArgumentException || ex is IOException) { encode = Encoding.UTF8; ModBase.Log("[ModPack] 已切换压缩包解压编码为 UTF8"); } // 检查加载器状态,决定是否中止 if (loader is not null && loader.LoadingState != MyLoading.MyLoadingState.Run) return; // 增加重试次数 retryCount++; if (retryCount <= 5) // 等待一段时间再重试 Thread.Sleep((retryCount - 1) * 2000); else throw new Exception("解压整合包文件失败", ex); } } /// /// 从整合包的 override 目录复制文件,同时设置 PCL 的配置文件与版本隔离。 /// 对路径末尾是否为 \ 没有要求。 /// private static void CopyOverrideDirectory(string overridesFolder, string versionFolder, LoaderBase loader, double progressIncrement) { if (!overridesFolder.EndsWithF(@"\")) overridesFolder += @"\"; if (!versionFolder.EndsWithF(@"\")) versionFolder += @"\"; // 复制文件 if (Directory.Exists(overridesFolder)) { ModBase.Log($"[ModPack] 处理整合包覆写文件夹:{overridesFolder} → {versionFolder}"); ModBase.CopyDirectory(overridesFolder, versionFolder, delta => loader.Progress += delta * progressIncrement); } else { ModBase.Log($"[ModPack] 整合包中没有覆写文件夹:{overridesFolder}"); loader.Progress += progressIncrement; } // 设置 ini var overridesIni = $@"{overridesFolder}PCL\Setup.ini"; var versionIni = $@"{versionFolder}PCL\Setup.ini"; if (File.Exists(overridesIni)) { ModBase.WriteIni(overridesIni, "VersionArgumentIndie", 1.ToString()); // 开启版本隔离 ModBase.WriteIni(overridesIni, "VersionArgumentIndieV2", true.ToString()); ModBase.CopyFile(overridesIni, versionIni); // 覆写已有的 ini } else { ModBase.WriteIni(versionIni, "VersionArgumentIndie", 1.ToString()); // 开启版本隔离 ModBase.WriteIni(versionIni, "VersionArgumentIndieV2", true.ToString()); } ModBase.IniClearCache(versionIni); // 重置缓存,避免被安装过程中写入的 ini 覆盖 } #region CurseForge private static LoaderCombo InstallPackCurseForge(string fileAddress, ZipArchive archive, string archiveBaseFolder, string instanceName = null, string logo = null, string resourceId = null, bool isOnlineInstall = false) { // 读取 Json 文件 JsonObject json; try { json = (JsonObject)ModBase.GetJson( ModBase.ReadFile(archive.GetEntry(archiveBaseFolder + "manifest.json").Open())); } catch (Exception ex) { throw new Exception("CurseForge 整合包安装信息存在问题", ex); } if (json["minecraft"] is null || json["minecraft"]["version"] is null) throw new Exception("CurseForge 整合包未提供 Minecraft 版本信息"); // 获取实例名 if (instanceName is null) { instanceName = (string)(json["name"] ?? ""); var validate = new FolderNameValidator(Path.Combine(ModFolder.mcFolderSelected, "versions")); if (!validate.Validate(instanceName).IsValid) instanceName = ""; if (string.IsNullOrEmpty(instanceName)) instanceName = ModMain.MyMsgBoxInput(Lang.Text("Minecraft.Download.Modpack.InputInstanceName"), "", "", [validate]); if (string.IsNullOrEmpty(instanceName)) throw new ModBase.CancelledException(); } // 获取 Mod API 版本信息 string forgeVersion = null; string neoForgeVersion = null; string fabricVersion = null; var modLoader = ModComp.CompLoaderType.Any; foreach (var Entry in (dynamic)json["minecraft"]["modLoaders"] ?? Array.Empty()) { string id = (Entry["id"] ?? "").ToString().ToLower(); if (id.StartsWithF("forge-")) { // Forge 指定 if (id.Contains("recommended")) throw new Exception(Lang.Text("Minecraft.Download.Modpack.TooOldUnsupported")); ModBase.Log("[ModPack] 整合包 Forge 版本:" + id); forgeVersion = id.Replace("forge-", ""); modLoader = ModComp.CompLoaderType.Forge; } else if (id.StartsWithF("neoforge-")) { // NeoForge 指定 ModBase.Log("[ModPack] 整合包 NeoForge 版本:" + id); neoForgeVersion = id.Replace("neoforge-", ""); modLoader = ModComp.CompLoaderType.NeoForge; } else if (id.StartsWithF("fabric-")) { // Fabric 指定 try { ModBase.Log("[ModPack] 整合包 Fabric 版本:" + id); fabricVersion = id.Replace("fabric-", ""); modLoader = ModComp.CompLoaderType.Fabric; break; } catch (Exception ex) { ModBase.Log(ex, "读取整合包 Fabric 版本失败:" + id); } } else if (id.StartsWithF("quilt-")) { throw new Exception(Lang.Text("Minecraft.Download.Modpack.QuiltUnsupported")); } } // 解压 var installTemp = ModMain.RequestTaskTempFolder(); var installLoaders = new List(); var overrideHome = (string)(json["overrides"] ?? ""); if (!string.IsNullOrEmpty(overrideHome)) installLoaders.Add(new LoaderTask(Lang.Text("Minecraft.Download.Modpack.Stage.ExtractModpack"), task => { ExtractModpackFiles(installTemp, fileAddress, task, 0.6d); CopyOverrideDirectory( Path.Combine(installTemp, archiveBaseFolder, overrideHome == "." || overrideHome == "./" ? "" : overrideHome), $@"{ModFolder.mcFolderSelected}versions\{instanceName}", task, 0.4d); }) { ProgressWeight = new FileInfo(fileAddress).Length / 1024d / 1024d / 6d, block = false }); // 每 6M 需要 1s // 获取 Mod 列表 var modList = new List(); var modOptionalList = new List(); foreach (var ModEntry in (dynamic)json["files"] ?? Array.Empty()) { if (ModEntry["projectID"] is null || ModEntry["fileID"] is null) { HintService.Hint(Lang.Text("Minecraft.Download.Modpack.ModMissingRequiredInfoSkipped", ModEntry)); continue; } modList.Add((int)ModEntry["fileID"]); if (ModEntry["required"] is JsonNode requiredNode && !requiredNode.ToObject()) modOptionalList.Add((int)ModEntry["fileID"]); } if (modList.Any()) { var modDownloadLoaders = new List(); // 获取 Mod 下载信息 modDownloadLoaders.Add(new LoaderTask( Lang.Text("Minecraft.Download.Modpack.Stage.PrepareModsDownloadInfo"), task => { var allowMirror = true; JsonArray ret; var tryCount = 0; do { tryCount += 1; ret = (JsonArray)((JsonObject)ModBase.GetJson(ModDownload.DlModRequest( "https://api.curseforge.com/v1/mods/files", "POST", "{\"fileIds\": [" + modList.Join(",") + "]}", "application/json", allowMirror)))["data"]; if (modList.Count <= ret.Count) { ModBase.Log("[Modpack] 已获取到的模组数量足够,开始进行下一步"); break; } allowMirror = false; ModBase.Log($"[Modpack] 获取模组数量不达标,设置镜像源允许状态为: {allowMirror}"); if (tryCount > 3) throw new Exception(Lang.Text("Minecraft.Download.Modpack.SomeModsDeleted")); } while (true); task.output = ret; }) { ProgressWeight = modList.Count / 10d }); // 每 10 Mod 需要 1s // 构造 NetFile modDownloadLoaders.Add(new LoaderTask>( Lang.Text("Minecraft.Download.Modpack.Stage.BuildModsDownloadInfo"), task => { var fileList = new Dictionary(); foreach (var ModJson in task.input) { var id = ModJson["id"].ToObject(); // 跳过重复的 Mod(疑似 CurseForge Bug) if (fileList.ContainsKey(id)) continue; // 可选 Mod 提示 if (modOptionalList.Contains(id)) if (ModMain.MyMsgBox( Lang.Text("Minecraft.Download.Modpack.OptionalFile.Message", ModJson["displayName"]), Lang.Text("Minecraft.Download.Modpack.OptionalFile.Title"), Lang.Text("Minecraft.Download.Modpack.OptionalFile.Download"), Lang.Text("Minecraft.Download.Modpack.OptionalFile.Skip") ) == 2) continue; // 根据 modules 和文件名后缀判断资源类型 string targetFolder; ModComp.CompType type; if (ModJson["modules"].AsArray().Any()) // modules 可能返回 null(#1006) { var moduleNames = ((JsonArray)ModJson["modules"]).Select(l => l["name"].ToString()).ToList(); if (moduleNames.Contains("META-INF") || moduleNames.Contains("mcmod.info") || (ModJson?["FileName"]?.ToString()?.EndsWithF(".jar", true)).GetValueOrDefault()) { targetFolder = "mods"; type = ModComp.CompType.Mod; } else if (moduleNames.Contains("pack.mcmeta")) { targetFolder = "resourcepacks"; type = ModComp.CompType.ResourcePack; } else if (moduleNames.Contains("level.dat")) { targetFolder = "saves"; type = ModComp.CompType.World; } else { targetFolder = "shaderpacks"; type = ModComp.CompType.Shader; } } else { targetFolder = "mods"; type = ModComp.CompType.Mod; } // 建立 CompFile var file = new ModComp.CompFile((JsonObject)ModJson, type); if (!file.Available) continue; // 实际的添加 fileList.Add(id, file.ToNetFile($@"{ModFolder.mcFolderSelected}versions\{instanceName}\{targetFolder}\", ModComp.DownloadReason.ModPack, json["minecraft"]!["version"]!.ToString(), modLoader)); task.Progress += 1d / (1 + modList.Count); } task.output = fileList.Values.ToList(); }) { ProgressWeight = modList.Count / 200d, show = false }); // 每 200 Mod 需要 1s // 下载 Mod 文件 modDownloadLoaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Modpack.Stage.DownloadMods"), []) { ProgressWeight = modList.Count * 1.5d }); // 每个 Mod 需要 1.5s // 构造加载器 installLoaders.Add( new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.DownloadMods.MainLoader"), modDownloadLoaders) { show = false, ProgressWeight = modDownloadLoaders.Sum(l => l.ProgressWeight) }); } // 构造加载器 var request = new ModDownloadLib.McInstallRequest { targetInstanceName = instanceName, targetInstanceFolder = $@"{ModFolder.mcFolderSelected}versions\{instanceName}\", minecraftName = json["minecraft"]["version"].ToString(), forgeVersion = forgeVersion, neoForgeVersion = neoForgeVersion, fabricVersion = fabricVersion, }; var mergeLoaders = ModDownloadLib.McInstallLoader(request); // 构造总加载器 var loaders = new List(); loaders.Add(new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.ModpackInstall"), installLoaders) { show = false, block = false, ProgressWeight = installLoaders.Sum(l => l.ProgressWeight) }); loaders.Add(new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.GameInstall"), mergeLoaders) { show = false, ProgressWeight = mergeLoaders.Sum(l => l.ProgressWeight) }); loaders.Add(new LoaderTask(Lang.Text("Minecraft.Download.Modpack.Stage.FinalizeFiles"), task => { // 设置图标 var versionFolder = $@"{ModFolder.mcFolderSelected}versions\{instanceName}\"; if (logo is not null && File.Exists(logo)) { File.Copy(logo, Path.Combine(versionFolder, "PCL", "Logo.png"), true); States.Instance.LogoPath[versionFolder] = @"PCL\Logo.png"; States.Instance.IsLogoCustom[versionFolder] = true; ModBase.Log("[ModPack] 已设置整合包 Logo:" + logo); } // 删除原始整合包文件 foreach (var Target in new[] { Path.Combine(versionFolder, "原始整合包.zip"), Path.Combine(versionFolder, "原始整合包.mrpack") }) if (File.Exists(Target)) { ModBase.Log("[ModPack] 删除原始整合包文件:" + Target); File.Delete(Target); } if (File.Exists(fileAddress) && ModBase.GetFileNameWithoutExtentionFromPath(fileAddress) == "modpack") { ModBase.Log("[ModPack] 删除安装整合包文件:" + fileAddress); File.Delete(fileAddress); } // 整合包版本 if (json["version"] is not null) States.Instance.ModpackVersion[versionFolder] = json["version"].ToString(); States.Instance.ModpackSource[versionFolder] = "CurseForge"; States.Instance.ModpackId[versionFolder] = resourceId; do { try { var projects = ModComp.CompRequest.GetCompProjectsByIds([resourceId]); if (projects.Count == 0) break; States.Instance.CustomInfo[versionFolder] = projects.First().Description; } catch (Exception ex) { ModBase.Log(ex, "[ModPack] 获取整合包描述文本失败"); } } while (false); }) { ProgressWeight = 0.1d, show = false }); // 重复任务检查 var loaderName = Lang.Text("Minecraft.Download.Modpack.Task.CurseForgeInstall", instanceName); if (loaderTaskbar.Any(l => (l.name ?? "") == (loaderName ?? ""))) { HintService.Hint(Lang.Text("Minecraft.Download.Modpack.Installing"), HintType.Error); throw new ModBase.CancelledException(); } // 启动 var loader = new LoaderCombo(loaderName, loaders) { OnStateChanged = ModDownloadLib.McInstallState }; loader.Start(request.targetInstanceFolder); LoaderTaskbarAdd(loader); ModMain.frmMain.BtnExtraDownload.ShowRefresh(); if (!isOnlineInstall) ModBase.RunInUi(() => ModMain.frmMain.PageChange(FormMain.PageType.TaskManager)); return loader; } #endregion #region Modrinth private static LoaderCombo InstallPackModrinth(string fileAddress, ZipArchive archive, string archiveBaseFolder, string instanceName = null, string logo = null, string resourceId = null, bool isOnlineInstall = false) { // 读取 Json 文件 JsonObject json; try { json = (JsonObject)ModBase.GetJson( ModBase.ReadFile(archive.GetEntry(archiveBaseFolder + "modrinth.index.json").Open())); } catch (Exception ex) { throw new Exception("Modrinth 整合包安装信息存在问题", ex); } if (json["dependencies"] is null || json["dependencies"]["minecraft"] is null) throw new Exception("Modrinth 整合包未提供 Minecraft 版本信息"); // 获取 Mod API 版本信息 string minecraftVersion = null; string forgeVersion = null; string neoForgeVersion = null; string fabricVersion = null; var modLoader = ModComp.CompLoaderType.Any; foreach (var Entry in json["dependencies"]?.AsObject() ?? new JsonObject()) switch (Entry.Key.ToLower() ?? "") { case "minecraft": { minecraftVersion = Entry.Value?.ToObject(); break; } case "forge": // eg. 14.23.5.2859 / 1.19-41.1.0 { forgeVersion = Entry.Value?.ToObject(); modLoader = ModComp.CompLoaderType.Forge; ModBase.Log("[ModPack] 整合包 Forge 版本:" + forgeVersion); break; } case "neoforge": case "neo-forge": // eg. 20.6.98-beta { neoForgeVersion = Entry.Value?.ToObject(); modLoader = ModComp.CompLoaderType.NeoForge; ModBase.Log("[ModPack] 整合包 NeoForge 版本:" + neoForgeVersion); break; } case "fabric-loader": // eg. 0.14.14 { fabricVersion = Entry.Value?.ToObject(); modLoader = ModComp.CompLoaderType.Fabric; ModBase.Log("[ModPack] 整合包 Fabric 版本:" + fabricVersion); break; } case "quilt-loader": // eg. 0.26.0 { throw new Exception(Lang.Text("Minecraft.Download.Modpack.QuiltUnsupported")); } default: { HintService.Hint(Lang.Text("Minecraft.Download.Modpack.UnknownLoader", Entry.Key, Entry.Value), HintType.Error); break; } } // 获取实例名 if (instanceName is null) { instanceName = (string)(json["name"] ?? ""); var validate = new FolderNameValidator(Path.Combine(ModFolder.mcFolderSelected, "versions")); if (!validate.Validate(instanceName).IsValid) instanceName = ""; if (string.IsNullOrEmpty(instanceName)) instanceName = ModMain.MyMsgBoxInput(Lang.Text("Minecraft.Download.Modpack.InputInstanceName"), "", "", [validate]); if (string.IsNullOrEmpty(instanceName)) throw new ModBase.CancelledException(); } // 解压 var installTemp = ModMain.RequestTaskTempFolder(); var installLoaders = new List(); installLoaders.Add(new LoaderTask(Lang.Text("Minecraft.Download.Modpack.Stage.ExtractModpack"), task => { ExtractModpackFiles(installTemp, fileAddress, task, 0.5d); CopyOverrideDirectory(Path.Combine(installTemp, archiveBaseFolder, "overrides"), Path.Combine(ModFolder.mcFolderSelected, "versions", instanceName), task, 0.4d); CopyOverrideDirectory(Path.Combine(installTemp, archiveBaseFolder, "client-overrides"), Path.Combine(ModFolder.mcFolderSelected, "versions", instanceName), task, 0.1d); }) { ProgressWeight = new FileInfo(fileAddress).Length / 1024d / 1024d / 6d, block = false }); // 每 6M 需要 1s // 获取下载文件列表 var fileList = new List(); foreach (var File in (dynamic)json["files"] ?? Array.Empty()) { // 检查是否需要该文件 if (File["env"] is not null) switch (File["env"]["client"].ToString() ?? "") { case "optional": { if (ModMain.MyMsgBox( Lang.Text("Minecraft.Download.Modpack.OptionalFile.Message", ModBase.GetFileNameFromPath(File["path"].ToString())), Lang.Text("Minecraft.Download.Modpack.OptionalFile.Title"), Lang.Text("Minecraft.Download.Modpack.OptionalFile.Download"), Lang.Text("Minecraft.Download.Modpack.OptionalFile.Skip") ) == 2) continue; break; } case "unsupported": { continue; } } // 添加下载文件 var urls = ((JsonArray)File["downloads"]) .OfType() .Select(x => ModComp.CompFile.HandleCurseForgeDownloadUrls(x.ToString())) .ToList(); // 镜像源 urls = urls.SelectMany(x => ModDownload.DlSourceModDownloadGet(x)).ToList(); var targetPath = $@"{ModFolder.mcFolderSelected}versions\{instanceName}\{File["path"]}"; if (!Path.GetFullPath(targetPath) .StartsWithF($@"{ModFolder.mcFolderSelected}versions\{instanceName}\", true)) { ModMain.MyMsgBox(Lang.Text("Minecraft.Download.Modpack.PathOutsideInstance.Message", targetPath), Lang.Text("Minecraft.Download.Modpack.PathOutsideInstance.Title"), isWarn: true); throw new ModBase.CancelledException(); } fileList.Add(new DownloadFile( ModComp.CompFile.HandleModrinthDownloadUrls(urls, ModComp.DownloadReason.ModPack, minecraftVersion, modLoader), targetPath, new ModBase.FileChecker(actualSize: ((JsonNode)File["fileSize"]).ToObject(), hash: File["hashes"]["sha1"].ToString()), true)); } if (fileList.Any()) installLoaders.Add( new LoaderDownload(Lang.Text("Minecraft.Download.Modpack.Stage.DownloadAdditions"), fileList) { ProgressWeight = fileList.Count * 1.5d }); // 每个 Mod 需要 1.5s // 构造加载器 var request = new ModDownloadLib.McInstallRequest { targetInstanceName = instanceName, targetInstanceFolder = $@"{ModFolder.mcFolderSelected}versions\{instanceName}\", minecraftName = minecraftVersion, forgeVersion = forgeVersion, neoForgeVersion = neoForgeVersion, fabricVersion = fabricVersion, }; var mergeLoaders = ModDownloadLib.McInstallLoader(request); // 构造总加载器 var loaders = new List(); loaders.Add(new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.ModpackInstall"), installLoaders) { show = false, block = false, ProgressWeight = installLoaders.Sum(l => l.ProgressWeight) }); loaders.Add(new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.GameInstall"), mergeLoaders) { show = false, ProgressWeight = mergeLoaders.Sum(l => l.ProgressWeight) }); loaders.Add(new LoaderTask(Lang.Text("Minecraft.Download.Modpack.Stage.FinalizeFiles"), task => { // 设置图标 var versionFolder = $@"{ModFolder.mcFolderSelected}versions\{instanceName}\"; if (logo is not null && File.Exists(logo)) { File.Copy(logo, Path.Combine(versionFolder, "PCL", "Logo.png"), true); States.Instance.LogoPath[versionFolder] = @"PCL\Logo.png"; States.Instance.IsLogoCustom[versionFolder] = true; ModBase.Log("[ModPack] 已设置整合包 Logo:" + logo); } // 删除原始整合包文件 foreach (var Target in new[] { Path.Combine(versionFolder, "原始整合包.zip"), Path.Combine(versionFolder, "原始整合包.mrpack") }) if (File.Exists(Target)) { ModBase.Log("[ModPack] 删除原始整合包文件:" + Target); File.Delete(Target); } if (File.Exists(fileAddress) && ModBase.GetFileNameWithoutExtentionFromPath(fileAddress) == "modpack") { ModBase.Log("[ModPack] 删除安装整合包文件:" + fileAddress); File.Delete(fileAddress); } // 整合包版本 if (json["versionId"] is not null) States.Instance.ModpackVersion[versionFolder] = json["versionId"].ToString(); States.Instance.ModpackSource[versionFolder] = "Modrinth"; States.Instance.ModpackId[versionFolder] = resourceId; do { try { var projects = ModComp.CompRequest.GetCompProjectsByIds([resourceId]); if (projects.Count == 0) break; States.Instance.CustomInfo[versionFolder] = projects.First().Description; } catch (Exception ex) { ModBase.Log(ex, "[ModPack] 获取整合包描述文本失败"); } } while (false); }) { ProgressWeight = 0.1d, show = false }); // 重复任务检查 var loaderName = Lang.Text("Minecraft.Download.Modpack.Task.ModrinthInstall", instanceName); if (loaderTaskbar.Any(l => (l.name ?? "") == (loaderName ?? ""))) { HintService.Hint(Lang.Text("Minecraft.Download.Modpack.Installing"), HintType.Error); throw new ModBase.CancelledException(); } // 启动 var loader = new LoaderCombo(loaderName, loaders) { OnStateChanged = ModDownloadLib.McInstallState }; loader.Start(request.targetInstanceFolder); LoaderTaskbarAdd(loader); ModMain.frmMain.BtnExtraDownload.ShowRefresh(); if (!isOnlineInstall) ModBase.RunInUi(() => ModMain.frmMain.PageChange(FormMain.PageType.TaskManager)); return loader; } #endregion #region HMCL private static LoaderCombo InstallPackHMCL(string fileAddress, ZipArchive archive, string archiveBaseFolder) { // 读取 Json 文件 JsonObject json; try { json = (JsonObject)ModBase.GetJson( ModBase.ReadFile(archive.GetEntry(archiveBaseFolder + "modpack.json").Open(), Encoding.UTF8)); } catch (Exception ex) { throw new Exception("HMCL 整合包安装信息存在问题", ex); } // 获取实例名 var instanceName = (string)(json["name"] ?? ""); var validate = new FolderNameValidator(Path.Combine(ModFolder.mcFolderSelected, "versions")); if (!validate.Validate(instanceName).IsValid) instanceName = ""; if (string.IsNullOrEmpty(instanceName)) instanceName = ModMain.MyMsgBoxInput(Lang.Text("Minecraft.Download.Modpack.InputInstanceName"), "", "", [validate]); if (string.IsNullOrEmpty(instanceName)) throw new ModBase.CancelledException(); // 解压 var installTemp = ModMain.RequestTaskTempFolder(); var installLoaders = new List(); installLoaders.Add(new LoaderTask(Lang.Text("Minecraft.Download.Modpack.Stage.ExtractModpack"), task => { ExtractModpackFiles(installTemp, fileAddress, task, 0.6d); CopyOverrideDirectory(Path.Combine(installTemp, archiveBaseFolder, "minecraft"), Path.Combine(ModFolder.mcFolderSelected, "versions", instanceName), task, 0.4d); }) { ProgressWeight = new FileInfo(fileAddress).Length / 1024d / 1024d / 6d, block = false }); // 每 6M 需要 1s // 构造游戏本体安装加载器 if (json["gameVersion"] is null) throw new Exception(Lang.Text("Minecraft.Download.Modpack.MissingGameVersion.Hmcl")); var request = new ModDownloadLib.McInstallRequest { targetInstanceName = instanceName, targetInstanceFolder = $@"{ModFolder.mcFolderSelected}versions\{instanceName}\", minecraftName = json["gameVersion"].ToString() }; var mergeLoaders = ModDownloadLib.McInstallLoader(request); // 构造总加载器 var loaders = new List { new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.ModpackInstall"), installLoaders) { show = false, block = false, ProgressWeight = installLoaders.Sum(l => l.ProgressWeight) }, new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.GameInstall"), mergeLoaders) { show = false, ProgressWeight = mergeLoaders.Sum(l => l.ProgressWeight) } }; // 重复任务检查 var loaderName = Lang.Text("Minecraft.Download.Modpack.Task.HmclInstall", instanceName); if (loaderTaskbar.Any(l => (l.name ?? "") == (loaderName ?? ""))) { HintService.Hint(Lang.Text("Minecraft.Download.Modpack.Installing"), HintType.Error); throw new ModBase.CancelledException(); } // 启动 var loader = new LoaderCombo(loaderName, loaders) { OnStateChanged = ModDownloadLib.McInstallState }; loader.Start(request.targetInstanceFolder); LoaderTaskbarAdd(loader); ModMain.frmMain.BtnExtraDownload.ShowRefresh(); ModBase.RunInUi(() => ModMain.frmMain.PageChange(FormMain.PageType.TaskManager)); return loader; } #endregion #region MCBBS private static LoaderCombo InstallPackMCBBS(string fileAddress, ZipArchive archive, string archiveBaseFolder, string instanceName = null) { // 读取 Json 文件 JsonObject json; try { // VB 的 If(a, b) 在 C# 中如果是 null 合并则用 ??,如果是三元运算则用 ?: var entry = archive.GetEntry(archiveBaseFolder + "mcbbs.packmeta") ?? archive.GetEntry(archiveBaseFolder + "manifest.json"); using (var stream = entry.Open()) { json = (JsonObject)ModBase.GetJson(ModBase.ReadFile(stream, Encoding.UTF8)); } } catch (Exception ex) { throw new Exception("MCBBS 整合包安装信息存在问题", ex); } // 获取实例名 if (instanceName is null) { instanceName = json["name"]?.ToString() ?? ""; var validate = new FolderNameValidator(Path.Combine(ModFolder.mcFolderSelected, "versions")); if (!validate.Validate(instanceName).IsValid) instanceName = ""; if (string.IsNullOrEmpty(instanceName)) instanceName = ModMain.MyMsgBoxInput(Lang.Text("Minecraft.Download.Modpack.InputInstanceName"), "", "", [validate]); if (string.IsNullOrEmpty(instanceName)) throw new ModBase.CancelledException(); } // 解压与路径准备 var installTemp = ModMain.RequestTaskTempFolder(); var versionFolder = $"{ModFolder.mcFolderSelected}versions\\{instanceName}"; var installLoaders = new List(); // 解压整合包文件任务 var unzipTask = new LoaderTask(Lang.Text("Minecraft.Download.Modpack.Stage.ExtractModpack"), task => { ExtractModpackFiles(installTemp, fileAddress, task, 0.6); CopyOverrideDirectory( Path.Combine(installTemp, archiveBaseFolder, "overrides"), Path.Combine(ModFolder.mcFolderSelected, "versions", instanceName), task, 0.4); // JVM 参数处理 if (json["launchInfo"] is not null) { var launchInfo = (JsonObject)json["launchInfo"]; Config.Instance.JvmArgs[versionFolder] = string.Join(" ", launchInfo["javaArgument"]); Config.Instance.GameArgs[versionFolder] = string.Join(" ", launchInfo["launchArgument"]); } // 整合包版本 if (json["version"] is not null) States.Instance.ModpackVersion[versionFolder] = json["version"].ToString(); }); unzipTask.ProgressWeight = new FileInfo(fileAddress).Length / 1024.0 / 1024.0 / 6.0; // 每 6M 需要 1s unzipTask.block = false; installLoaders.Add(unzipTask); // 构造加载器 if (json["addons"] is null) throw new Exception(Lang.Text("Minecraft.Download.Modpack.MissingGameVersion.McbbsAddons")); var addons = new Dictionary(); foreach (var EntryNode in json["addons"].AsArray()) { var entry = EntryNode.AsObject(); addons.Add(entry["id"].ToString(), entry["version"].ToString()); } if (!addons.ContainsKey("game")) { HintService.Hint(Lang.Text("Minecraft.Download.Modpack.MissingGameVersion.Generic"), HintType.Error); return null; } if (addons.ContainsKey("quilt")) throw new Exception(Lang.Text("Minecraft.Download.Modpack.QuiltUnsupported")); // 构造安装请求 var request = new ModDownloadLib.McInstallRequest { targetInstanceName = instanceName, targetInstanceFolder = $"{ModFolder.mcFolderSelected}versions\\{instanceName}\\", minecraftName = addons["game"], optiFineVersion = addons.ContainsKey("optifine") ? addons["optifine"] : null, forgeVersion = addons.ContainsKey("forge") ? addons["forge"] : null, neoForgeVersion = addons.ContainsKey("neoforge") ? addons["neoforge"] : null, fabricVersion = addons.ContainsKey("fabric") ? addons["fabric"] : null, }; var mergeLoaders = ModDownloadLib.McInstallLoader(request); // 构造总加载器 var loaders = new List(); loaders.Add(new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.ModpackInstall"), installLoaders) { show = false, block = false, ProgressWeight = installLoaders.Sum(l => l.ProgressWeight) }); loaders.Add(new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.GameInstall"), mergeLoaders) { show = false, ProgressWeight = mergeLoaders.Sum(l => l.ProgressWeight) }); // 重复任务检查 var loaderName = Lang.Text("Minecraft.Download.Modpack.Task.McbbsInstall", instanceName); if (loaderTaskbar.Any(l => l.name == loaderName)) { HintService.Hint(Lang.Text("Minecraft.Download.Modpack.Installing"), HintType.Error); throw new ModBase.CancelledException(); } // 启动任务 var loader = new LoaderCombo(loaderName, loaders); loader.OnStateChanged = ModDownloadLib.McInstallState; loader.Start(request.targetInstanceFolder); LoaderTaskbarAdd(loader); ModMain.frmMain.BtnExtraDownload.ShowRefresh(); ModBase.RunInUi(() => ModMain.frmMain.PageChange(FormMain.PageType.TaskManager)); return loader; } #endregion #region 带启动器的压缩包 private static LoaderCombo InstallPackLauncherPack(string fileAddress, ZipArchive archive, string archiveBaseFolder) { // 获取解压路径 ModMain.MyMsgBox(Lang.Text("Minecraft.Download.Modpack.SelectEmptyFolder.Message"), Lang.Text("Common.Action.Install"), Lang.Text("Common.Action.Continue"), forceWait: true); var targetFolder = SystemDialogs.SelectFolder(Lang.Text("Minecraft.Download.Modpack.SelectTargetFolder.Title")); if (string.IsNullOrEmpty(targetFolder)) throw new ModBase.CancelledException(); if (Directory.GetFileSystemEntries(targetFolder).Length > 0) { HintService.Hint(Lang.Text("Minecraft.Download.Modpack.TargetFolderMustBeEmpty"), HintType.Error); throw new ModBase.CancelledException(); } // 解压 var loader = new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.ExtractArchive"), new[] { new LoaderTask(Lang.Text("Minecraft.Download.Modpack.Stage.ExtractArchive"), task => { ExtractModpackFiles(targetFolder, fileAddress, task, 0.9d); Thread.Sleep(400); // 避免文件争用 // 查找解压后的 exe 文件 string launcher = null; foreach (var ExeFile in Directory.GetFiles(targetFolder, "*.exe", SearchOption.TopDirectoryOnly)) { var info = FileVersionInfo.GetVersionInfo(ExeFile); ModBase.Log($"[Modpack] 文件 {ExeFile} 的产品名标识为 {info.ProductName}"); if (info.ProductName == "Plain Craft Launcher") { launcher = ExeFile; ModBase.Log($"[Modpack] 发现整合包附带的 PCL 启动器:{ExeFile}"); } else if ((info.ProductName.ContainsF("Launcher", true) || info.ProductName.ContainsF("启动", true)) && !(info.ProductName == "Plain Craft Launcher Admin Manager")) { if (launcher is null) { launcher = ExeFile; ModBase.Log($"[Modpack] 发现整合包附带的疑似第三方启动器:{ExeFile}"); } } } task.Progress = 0.95d; // 尝试使用附带的启动器打开 if (launcher is not null) { ModBase.Log("[Modpack] 找到压缩包中附带的启动器:" + launcher); if (ModMain.MyMsgBox(Lang.Text("Minecraft.Download.Modpack.BundledLauncher.Message", launcher), Lang.Text("Minecraft.Download.Modpack.BundledLauncher.Title"), Lang.Text("Minecraft.Download.Modpack.BundledLauncher.UseBundled"), Lang.Text("Minecraft.Download.Modpack.BundledLauncher.DoNotUse") ) == 1) { ModBase.OpenExplorer(targetFolder); ModBase.ShellOnly(launcher, "--wait"); // 要求等待已有的 PCL 退出 ModBase.Log("[Modpack] 为换用整合包中的启动器启动,强制结束程序"); ModMain.frmMain.EndProgram(false); return; } } else { ModBase.Log("[Modpack] 未找到压缩包中附带的启动器"); } ModBase.OpenExplorer(targetFolder); // 加入文件夹列表 var instanceName = ModBase.GetFolderNameFromPath(targetFolder); Directory.CreateDirectory(Path.Combine(targetFolder, ".minecraft")); PageSelectLeft.AddFolder( Path.Combine(targetFolder, ".minecraft", archiveBaseFolder.Replace("/", @"\").TrimStart('\\')), instanceName, false); // 格式例如:包裹文件夹\.minecraft\(最短为空字符串) // 调用 modpack 文件进行安装 var modpackFile = Directory.GetFiles(targetFolder, "modpack.*", SearchOption.AllDirectories).First(); ModBase.Log("[Modpack] 调用 modpack 文件继续安装:" + modpackFile); ModpackInstall(modpackFile); }) }); loader.Start(targetFolder); LoaderTaskbarAdd(loader); ModMain.frmMain.BtnExtraDownload.ShowRefresh(); ModMain.frmMain.BtnExtraDownload.Ribble(); return loader; } #endregion #region 普通压缩包 private static LoaderCombo InstallPackCompress(string fileAddress, ZipArchive archive) { // 尝试定位 .minecraft 文件夹:寻找形如 “/versions/XXX/XXX.json” 的路径 Match match = null; var regex = new Regex(@"^.*\/(?=versions\/(?[^\/]+)\/(\k)\.json$)", RegexOptions.IgnoreCase); foreach (var Entry in archive.Entries) { var entryMatch = regex.Match("/" + Entry.FullName); if (entryMatch.Success) { match = entryMatch; break; } } if (match is null) throw new Exception(Lang.Text("Minecraft.Download.Modpack.UnknownArchiveStructure")); // 没有匹配 var archiveBaseFolder = match.Value.Replace("/", @"\").TrimStart('\\'); // 格式例如:包裹文件夹\.minecraft\(最短为空字符串) var instanceName = match.Groups[1].Value; ModBase.Log("[ModPack] 检测到压缩包的 .minecraft 根目录:" + archiveBaseFolder + ",命中的实例名:" + instanceName); // 获取解压路径 ModMain.MyMsgBox(Lang.Text("Minecraft.Download.Modpack.SelectEmptyFolder.Message"), Lang.Text("Common.Action.Install"), Lang.Text("Common.Action.Continue"), forceWait: true); var targetFolder = SystemDialogs.SelectFolder(Lang.Text("Minecraft.Download.Modpack.SelectTargetFolder.Title")); if (string.IsNullOrEmpty(targetFolder)) throw new ModBase.CancelledException(); if (targetFolder.Contains("!") || targetFolder.Contains(";")) { HintService.Hint(Lang.Text("Minecraft.Download.Modpack.InvalidGamePathChars", targetFolder), HintType.Error); throw new ModBase.CancelledException(); } if (Directory.GetFileSystemEntries(targetFolder).Length > 0) { HintService.Hint(Lang.Text("Minecraft.Download.Modpack.TargetFolderMustBeEmpty"), HintType.Error); throw new ModBase.CancelledException(); } // 解压 var loader = new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.ExtractArchive"), new[] { new LoaderTask(Lang.Text("Minecraft.Download.Modpack.Stage.ExtractArchive"), task => { ExtractModpackFiles(targetFolder, fileAddress, task, 0.95d); // 加入文件夹列表 PageSelectLeft.AddFolder(Path.Combine(targetFolder, archiveBaseFolder), ModBase.GetFolderNameFromPath(targetFolder), false); Thread.Sleep(400); // 避免文件争用 ModBase.RunInUi(() => ModMain.frmMain.PageChange(FormMain.PageType.InstanceSelect)); }) }) { OnStateChanged = ModDownloadLib.McInstallState }; loader.Start(targetFolder); LoaderTaskbarAdd(loader); ModMain.frmMain.BtnExtraDownload.ShowRefresh(); ModMain.frmMain.BtnExtraDownload.Ribble(); return loader; } #endregion #region MultiMC public class MMCPackInfo { public JsonObject additionalJson = new(); public bool isCleanroomOverrided; public bool isFabricOverrided; public bool isForgeOverrided; public bool isMcArgsEdited; public bool isMinecraftOverrided; public bool isNeoForgeOverrided; public JsonArray jvmArgs = new(); public JsonArray libraries = new(); public JsonObject overridedJson = new(); public string tweakers = null; } private static LoaderCombo InstallPackMMC(string fileAddress, ZipArchive archive, string archiveBaseFolder) { // 读取 Json 文件 JsonObject packJson; string packInstance; MMCPackInfo packInfo = null; try { packJson = (JsonObject)ModBase.GetJson( ModBase.ReadFile(archive.GetEntry(archiveBaseFolder + "mmc-pack.json").Open(), Encoding.UTF8)); packInstance = ModBase.ReadFile(archive.GetEntry(archiveBaseFolder + "instance.cfg").Open(), Encoding.UTF8); #region JSON Patches // 参考 https://github.com/MultiMC/Launcher/wiki/JSON-Patches do { try { if (!archive.Entries.Any(e => e.FullName.Equals(archiveBaseFolder + "patches/", StringComparison.OrdinalIgnoreCase))) break; ModBase.Log("[ModPack] 安装的 MultiMC 整合包存在 JSON Patches"); // 排序预处理 var patches = new List>(); foreach (var entry in archive.Entries) if (!entry.FullName.EndsWith("/") && entry.FullName.StartsWith(archiveBaseFolder + "patches/")) { var patch = (JsonObject)ModBase.GetJson(ModBase.ReadFile( archive.GetEntry(entry.FullName).Open(), Encoding.UTF8)); patches.Add(new KeyValuePair(patch, (int)(patch["order"] is not null ? patch["order"] : 0))); } var components = (JsonArray)packJson["components"]; var componentUids = components .Select(c => c["uid"]?.ToString()) .ToHashSet(); patches = patches .Where(p => componentUids.Contains(p.Key["uid"]?.ToString())) .OrderBy(p => p.Value) .ToList(); // 应用 Patches packInfo = new MMCPackInfo(); string tweakers = null; JsonObject assetIndex = null; JsonObject javaVerJson = null; string mainClass = null; var gameArguments = new JsonArray(); var jvmArguments = new JsonArray(); var libJson = new JsonArray(); var addLibJson = new JsonArray(); foreach (var Patch in patches) { var patchJson = Patch.Key; if ((string)patchJson["uid"] == "net.minecraft") { packInfo.isMinecraftOverrided = true; } else if ((string)patchJson["uid"] == "net.minecraftforge") { if (patchJson["version"].ToString().StartsWithF("0.")) packInfo.isCleanroomOverrided = true; else packInfo.isForgeOverrided = true; } else if ((string)patchJson["uid"] == "net.neoforged") { packInfo.isNeoForgeOverrided = true; } else if ((string)patchJson["uid"] == "net.fabricmc.fabric-loader") { packInfo.isFabricOverrided = true; } else if ((string)patchJson["uid"] == "org.quiltmc.quilt-loader") { throw new Exception(Lang.Text("Minecraft.Download.Modpack.QuiltUnsupported")); } // JVM 参数 if (patchJson["+jvmArgs"] is not null) { jvmArguments.Merge(patchJson["+jvmArgs"]); ModBase.Log($"[ModPack] 已应用 JSON-Patch {patchJson["uid"]} 的 JVM 参数"); } // Libraries if (patchJson["libraries"] is not null || patchJson["+libraries"] is not null) { var libs = new JsonArray(); if (patchJson["libraries"] is not null) foreach (var Library in patchJson["libraries"].AsArray()) { if (Library is not JsonObject LibraryObj) continue; var libJobj = LibraryObj.DeepClone().AsObject(); if (libJobj["MMC-hint"] is not null) { libJobj.Add("hint", libJobj["MMC-hint"]?.DeepClone()); libJobj.Remove("MMC-hint"); } libs.Add(libJobj); } if (patchJson["+libraries"] is not null) foreach (var Library in patchJson["+libraries"].AsArray()) // TODO: 此处处理不严谨,但也能用吧 { if (Library is not JsonObject LibraryObj) continue; var libJobj = LibraryObj.DeepClone().AsObject(); if (libJobj["MMC-hint"] is not null) { libJobj.Add("hint", libJobj["MMC-hint"]?.DeepClone()); libJobj.Remove("MMC-hint"); } libs.Add(libJobj); } libJson.Merge(libs); ModBase.Log($"[ModPack] 已应用 JSON-Patch {patchJson["uid"]} 的 Libraries"); } // Tweakers if (patchJson["+tweakers"] is not null) { tweakers = (string)patchJson["+tweakers"][0]; ModBase.Log($"[ModPack] 已应用 JSON-Patch {patchJson["uid"]} 的 Tweakers"); } // AssetIndex if (patchJson["assetIndex"] is not null) { assetIndex = patchJson["assetIndex"]?.DeepClone().AsObject(); ModBase.Log($"[ModPack] 已应用 JSON-Patch {patchJson["uid"]} 的 AssetIndex"); } // minecraftArguments -> arguments.game if (patchJson["minecraftArguments"] is not null) { foreach (var Arg in patchJson["minecraftArguments"].ToString().Split(" ")) gameArguments.Add(Arg); packInfo.isMcArgsEdited = true; ModBase.Log( $"[ModPack] 已应用 JSON-Patch {patchJson["uid"]} 的 minecraftArguments 至 arguments.game"); } // mainClass if (patchJson["mainClass"] is not null) { mainClass = (string)patchJson["mainClass"]; ModBase.Log($"[ModPack] 已应用 JSON-Patch {patchJson["uid"]} 的 mainClass"); } // Java 版本要求 if (patchJson["compatibleJavaMajors"] is not null) { var javaVersion = 0; string javaComponent = null; var javaMajors = (JsonArray)patchJson["compatibleJavaMajors"]; foreach (var Java in javaMajors) { if (javaVersion > ModBase.Val(Java)) continue; // 优先选择主要的版本 if (ModBase.Val(Java) == 21d) { javaVersion = 21; javaComponent = "java-runtime-delta"; } else if (ModBase.Val(Java) == 17d) { javaVersion = 17; javaComponent = "java-runtime-gamma"; } else if (ModBase.Val(Java) == 11d) { javaVersion = 11; javaComponent = null; } else if (ModBase.Val(Java) == 8d) { javaVersion = 8; javaComponent = "jre-legacy"; } } if (javaVersion == 0) { javaVersion = (int)javaMajors[0]; javaComponent = null; } javaVerJson = new JsonObject { { "majorVersion", javaVersion } }; if (javaComponent is not null) javaVerJson.Add("component", javaComponent); ModBase.Log($"[ModPack] JSON-Patch {patchJson["uid"]} 要求 Java 版本: " + javaVersion); } } JsonObject jsonArguments = null; if (!string.IsNullOrWhiteSpace(tweakers)) { gameArguments.Add("--tweakClass"); gameArguments.Add(tweakers); } if (gameArguments is not null || jvmArguments is not null) { jvmArguments.Insert(0, "-Djava.library.path=${natives_directory}"); jvmArguments.Insert(1, "-Dminecraft.launcher.brand=${launcher_name}"); jvmArguments.Insert(2, "-Dminecraft.launcher.version=${launcher_version}"); jvmArguments.Insert(3, "-cp"); jvmArguments.Insert(4, "${classpath}"); jsonArguments = new JsonObject { { "game", gameArguments }, { "jvm", jvmArguments } }; } packInfo.overridedJson = new JsonObject(); if (jsonArguments is not null) packInfo.overridedJson.Add("arguments", jsonArguments); if (mainClass is not null) packInfo.overridedJson.Add("mainClass", mainClass); if (assetIndex is not null) packInfo.overridedJson.Add("assetIndex", assetIndex); if (javaVerJson is not null) packInfo.overridedJson.Add("javaVersion", javaVerJson); if (libJson is not null) packInfo.overridedJson.Add("libraries", libJson); } catch (Exception ex) { ModBase.Log(ex, "应用 MMC JSON-Patches 失败"); } } while (false); } #endregion catch (Exception ex) { throw new Exception("MMC 整合包安装信息存在问题", ex); } // 获取实例名 var instanceName = packInstance.RegexSeek(@"(?<=\nname\=)[^\n]+") ?? ""; var validate = new FolderNameValidator(Path.Combine(ModFolder.mcFolderSelected, "versions")); if (!validate.Validate(instanceName).IsValid) instanceName = ""; if (string.IsNullOrEmpty(instanceName)) instanceName = ModMain.MyMsgBoxInput(Lang.Text("Minecraft.Download.Modpack.InputInstanceName"), "", "", [validate]); if (string.IsNullOrEmpty(instanceName)) throw new ModBase.CancelledException(); // 解压 var installTemp = ModMain.RequestTaskTempFolder(); var versionFolder = $@"{ModFolder.mcFolderSelected}versions\{instanceName}"; var installLoaders = new List(); installLoaders.Add(new LoaderTask(Lang.Text("Minecraft.Download.Modpack.Stage.ExtractModpack"), task => { ExtractModpackFiles(installTemp, fileAddress, task, 0.55d); CopyOverrideDirectory(Path.Combine(installTemp, archiveBaseFolder, "libraries"), Path.Combine(ModFolder.mcFolderSelected, "versions", instanceName, "libraries"), task, 0.2d); CopyOverrideDirectory(Path.Combine(installTemp, archiveBaseFolder, ".minecraft"), Path.Combine(ModFolder.mcFolderSelected, "versions", instanceName), task, 0.2d); #region instance.cfg // 读取 MMC 设置文件(#2655) try { var mMCSetupFile = Path.Combine(installTemp, archiveBaseFolder, "instance.cfg"); // 将其中的等号替换为冒号,以符合 ini 文件格式 if (File.Exists(mMCSetupFile)) { List lines = []; foreach (var Line in ModBase.ReadFile(mMCSetupFile).Split(new[] { "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries)) { if (!Line.Contains("=")) continue; lines.Add(Line.BeforeFirst("=") + ":" + Line.AfterFirst("=")); } ModBase.WriteFile(mMCSetupFile, lines.Join("\r\n")); // 读取文件 if (Convert.ToBoolean(ModBase.ReadIni(mMCSetupFile, "OverrideCommands", false.ToString()))) { var preLaunchCommand = ModBase.ReadIni(mMCSetupFile, "PreLaunchCommand"); if (!string.IsNullOrEmpty(preLaunchCommand)) { preLaunchCommand = preLaunchCommand.Replace(@"\""", "\"") .Replace("$INST_JAVA", "{java}java.exe").Replace(@"$INST_MC_DIR\", "{minecraft}") .Replace("$INST_MC_DIR", "{minecraft}").Replace(@"$INST_DIR\", "{verpath}") .Replace("$INST_DIR", "{verpath}").Replace("$INST_ID", "{name}") .Replace("$INST_NAME", "{name}"); Config.Instance.PreLaunchCommand[versionFolder] = preLaunchCommand; ModBase.Log("[ModPack] 迁移 MultiMC 实例独立设置:启动前执行命令:" + preLaunchCommand); } } if (Convert.ToBoolean(ModBase.ReadIni(mMCSetupFile, "JoinServerOnLaunch", false.ToString()))) { var serverAddress = ModBase.ReadIni(mMCSetupFile, "JoinServerOnLaunchAddress") .Replace(@"\""", "\""); Config.Instance.ServerToEnter[versionFolder] = serverAddress; ModBase.Log("[ModPack] 迁移 MultiMC 实例独立设置:自动进入服务器:" + serverAddress); } if (Convert.ToBoolean(ModBase.ReadIni(mMCSetupFile, "IgnoreJavaCompatibility", false.ToString()))) { Config.Instance.IgnoreJavaCompatibility[versionFolder] = true; ModBase.Log("[ModPack] 迁移 MultiMC 实例独立设置:忽略 Java 兼容性警告"); } var logo = Path.GetFileName(ModBase.ReadIni(mMCSetupFile, "iconKey")); if (!string.IsNullOrEmpty(logo) && File.Exists($"{installTemp}{archiveBaseFolder}{logo}.png")) { States.Instance.IsLogoCustom[versionFolder] = true; States.Instance.LogoPath[versionFolder] = @"PCL\Logo.png"; ModBase.CopyFile($"{installTemp}{archiveBaseFolder}{logo}.png", $@"{ModFolder.mcFolderSelected}versions\{instanceName}\PCL\Logo.png"); ModBase.Log($"[ModPack] 迁移 MultiMC 实例独立设置:实例图标({logo}.png)"); } // JVM 参数 var jvmArgs = ModBase.ReadIni(mMCSetupFile, "JvmArgs"); if (!string.IsNullOrEmpty(jvmArgs)) { if (Convert.ToBoolean(ModBase.ReadIni(mMCSetupFile, "OverrideJavaArgs", false.ToString()))) { Config.Instance.JvmArgs[versionFolder] = jvmArgs; ModBase.Log("[ModPack] 迁移 MultiMC 实例独立设置:JVM 参数(覆盖):" + jvmArgs); } else { jvmArgs = jvmArgs + " " + Config.Launch.JvmArgs; Config.Instance.JvmArgs[versionFolder] = jvmArgs; ModBase.Log("[ModPack] 迁移 MultiMC 实例独立设置:JVM 参数(追加):" + jvmArgs); } } } } catch (Exception ex) { ModBase.Log(ex, $"读取 MMC 配置文件失败({installTemp}{archiveBaseFolder}instance.cfg)"); } #endregion }) { ProgressWeight = new FileInfo(fileAddress).Length / 1024d / 1024d / 6d, block = false }); // 每 6M 需要 1s // 构造实例安装请求 if (packJson["components"] is null) throw new Exception(Lang.Text("Minecraft.Download.Modpack.MissingGameVersion.Generic")); var request = new ModDownloadLib.McInstallRequest { targetInstanceName = instanceName, targetInstanceFolder = $@"{ModFolder.mcFolderSelected}versions\{instanceName}\" }; foreach (var Component in packJson["components"].AsArray()) switch ((Component["uid"] ?? "").ToString() ?? "") { case "org.lwjgl": { ModBase.Log("[ModPack] 已跳过 LWJGL 项"); break; } case "net.minecraft": { request.minecraftName = (string)Component["version"]; break; } case "net.minecraftforge": { if (Component["version"].ToString().StartsWithF("0.")) request.cleanroomVersion = (string)Component["version"]; else request.forgeVersion = (string)Component["version"]; break; } case "net.neoforged": { request.neoForgeVersion = (string)Component["version"]; break; } case "net.fabricmc.fabric-loader": { request.fabricVersion = (string)Component["version"]; break; } case "org.quiltmc.quilt-loader": { throw new Exception(Lang.Text("Minecraft.Download.Modpack.QuiltUnsupported")); } } if (packInfo is not null) request.mmcPackInfo = packInfo; // 构造加载器 var mergeLoaders = ModDownloadLib.McInstallLoader(request); // 构造总加载器 var loaders = new List(); loaders.Add(new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.ModpackInstall"), installLoaders) { show = false, block = false, ProgressWeight = installLoaders.Sum(l => l.ProgressWeight) }); loaders.Add(new LoaderCombo(Lang.Text("Minecraft.Download.Modpack.Stage.GameInstall"), mergeLoaders) { show = false, ProgressWeight = mergeLoaders.Sum(l => l.ProgressWeight) }); // 重复任务检查 var loaderName = Lang.Text("Minecraft.Download.Modpack.Task.MmcInstall", instanceName); if (loaderTaskbar.Any(l => (l.name ?? "") == (loaderName ?? ""))) { HintService.Hint(Lang.Text("Minecraft.Download.Modpack.Installing"), HintType.Error); throw new ModBase.CancelledException(); } // 启动 var loader = new LoaderCombo(loaderName, loaders) { OnStateChanged = ModDownloadLib.McInstallState }; loader.Start(request.targetInstanceFolder); LoaderTaskbarAdd(loader); ModMain.frmMain.BtnExtraDownload.ShowRefresh(); ModBase.RunInUi(() => ModMain.frmMain.PageChange(FormMain.PageType.TaskManager)); return loader; } #endregion }