Files
xyou f70b061d1a
CI / Go Backend (push) Canceled after 0s
feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
2026-08-08 14:07:56 +08:00

1725 lines
77 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
// 触发整合包安装的外部接口
/// <summary>
/// 弹窗要求选择一个整合包文件并进行安装。
/// </summary>
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"));
}
});
}
/// <summary>
/// 构建并启动安装给定的整合包文件的加载器,并返回该加载器。若失败则抛出异常。
/// 必须在工作线程执行。
/// </summary>
/// <exception cref="ModBase.CancelledException" />
public static LoaderCombo<string> 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);
}
}
/// <summary>
/// 从整合包的 override 目录复制文件,同时设置 PCL 的配置文件与版本隔离。
/// 对路径末尾是否为 \ 没有要求。
/// </summary>
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<string> 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<JsonNode>())
{
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<LoaderBase>();
var overrideHome = (string)(json["overrides"] ?? "");
if (!string.IsNullOrEmpty(overrideHome))
installLoaders.Add(new LoaderTask<string, int>(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<int>();
var modOptionalList = new List<int>();
foreach (var ModEntry in (dynamic)json["files"] ?? Array.Empty<JsonNode>())
{
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<bool>())
modOptionalList.Add((int)ModEntry["fileID"]);
}
if (modList.Any())
{
var modDownloadLoaders = new List<LoaderBase>();
// 获取 Mod 下载信息
modDownloadLoaders.Add(new LoaderTask<int, JsonArray>(
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<JsonArray, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Modpack.Stage.BuildModsDownloadInfo"), task =>
{
var fileList = new Dictionary<int, DownloadFile>();
foreach (var ModJson in task.input)
{
var id = ModJson["id"].ToObject<int>();
// 跳过重复的 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<int>(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<LoaderBase>();
loaders.Add(new LoaderCombo<string>(Lang.Text("Minecraft.Download.Modpack.Stage.ModpackInstall"),
installLoaders)
{ show = false, block = false, ProgressWeight = installLoaders.Sum(l => l.ProgressWeight) });
loaders.Add(new LoaderCombo<string>(Lang.Text("Minecraft.Download.Modpack.Stage.GameInstall"), mergeLoaders)
{ show = false, ProgressWeight = mergeLoaders.Sum(l => l.ProgressWeight) });
loaders.Add(new LoaderTask<string, string>(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<string>(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<string> 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<string>();
break;
}
case "forge": // eg. 14.23.5.2859 / 1.19-41.1.0
{
forgeVersion = Entry.Value?.ToObject<string>();
modLoader = ModComp.CompLoaderType.Forge;
ModBase.Log("[ModPack] 整合包 Forge 版本:" + forgeVersion);
break;
}
case "neoforge":
case "neo-forge": // eg. 20.6.98-beta
{
neoForgeVersion = Entry.Value?.ToObject<string>();
modLoader = ModComp.CompLoaderType.NeoForge;
ModBase.Log("[ModPack] 整合包 NeoForge 版本:" + neoForgeVersion);
break;
}
case "fabric-loader": // eg. 0.14.14
{
fabricVersion = Entry.Value?.ToObject<string>();
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<LoaderBase>();
installLoaders.Add(new LoaderTask<string, int>(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<DownloadFile>();
foreach (var File in (dynamic)json["files"] ?? Array.Empty<JsonNode>())
{
// 检查是否需要该文件
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<JsonNode>()
.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<long>(),
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<LoaderBase>();
loaders.Add(new LoaderCombo<string>(Lang.Text("Minecraft.Download.Modpack.Stage.ModpackInstall"),
installLoaders)
{ show = false, block = false, ProgressWeight = installLoaders.Sum(l => l.ProgressWeight) });
loaders.Add(new LoaderCombo<string>(Lang.Text("Minecraft.Download.Modpack.Stage.GameInstall"), mergeLoaders)
{ show = false, ProgressWeight = mergeLoaders.Sum(l => l.ProgressWeight) });
loaders.Add(new LoaderTask<string, string>(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<string>(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<string> 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<LoaderBase>();
installLoaders.Add(new LoaderTask<string, int>(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<LoaderBase>
{
new LoaderCombo<string>(Lang.Text("Minecraft.Download.Modpack.Stage.ModpackInstall"), installLoaders)
{ show = false, block = false, ProgressWeight = installLoaders.Sum(l => l.ProgressWeight) },
new LoaderCombo<string>(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<string>(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<string> 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<LoaderBase>();
// 解压整合包文件任务
var unzipTask = new LoaderTask<string, int>(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<string, string>();
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<LoaderBase>();
loaders.Add(new LoaderCombo<string>(Lang.Text("Minecraft.Download.Modpack.Stage.ModpackInstall"),
installLoaders)
{
show = false,
block = false,
ProgressWeight = installLoaders.Sum(l => l.ProgressWeight)
});
loaders.Add(new LoaderCombo<string>(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<string>(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<string> 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<string>(Lang.Text("Minecraft.Download.Modpack.Stage.ExtractArchive"), new[]
{
new LoaderTask<string, int>(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<string> InstallPackCompress(string fileAddress, ZipArchive archive)
{
// 尝试定位 .minecraft 文件夹:寻找形如 “/versions/XXX/XXX.json” 的路径
Match match = null;
var regex = new Regex(@"^.*\/(?=versions\/(?<ver>[^\/]+)\/(\k<ver>)\.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<string>(Lang.Text("Minecraft.Download.Modpack.Stage.ExtractArchive"), new[]
{
new LoaderTask<string, int>(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<string> 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<KeyValuePair<JsonObject, int>>();
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<JsonObject, int>(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<LoaderBase>();
installLoaders.Add(new LoaderTask<string, int>(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<string> 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<LoaderBase>();
loaders.Add(new LoaderCombo<string>(Lang.Text("Minecraft.Download.Modpack.Stage.ModpackInstall"),
installLoaders)
{ show = false, block = false, ProgressWeight = installLoaders.Sum(l => l.ProgressWeight) });
loaders.Add(new LoaderCombo<string>(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<string>(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
}