Files
MRCC/launcher/PCL-CE/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.cs
T

4579 lines
206 KiB
C#
Raw Normal View History

using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using PCL.Core.App;
using PCL.Core.App.Configuration;
using PCL.Core.App.Configuration.Storage;
using PCL.Core.App.Localization;
using PCL.Core.IO.Net.Http;
using PCL.Core.Minecraft;
using PCL.Core.UI;
using PCL.Core.Utils;
using PCL.Network;
using PCL.Network.Loaders;
namespace PCL;
public static class ModDownloadLib
{
/// <summary>
/// 如果 OptiFine 与 Forge 同时开始安装,就会导致 Forge 安装失败。
/// </summary>
private static readonly object installSyncLock = new();
/// <summary>
/// 如果 OptiFine 与 Forge 同时复制原版 Jar,就会导致复制文件时冲突。
/// </summary>
private static readonly object vanillaSyncLock = new();
/// <summary>
/// 将远程元数据提供的名称作为单个目录名拼接到缓存目录下,并阻止路径穿越。
/// </summary>
private static string CombineCacheSubfolder(string parentFolder, string childFolderName)
{
if (string.IsNullOrWhiteSpace(childFolderName) || childFolderName is "." or ".." ||
childFolderName.IndexOfAny(new[] { '/', '\\', ':', '*', '?', '"', '<', '>', '|', '\0' }) >= 0 ||
Path.IsPathRooted(childFolderName))
CancelUnsafeCacheSubfolder(childFolderName, "包含非法路径字符");
var parentFullPath = Path.GetFullPath(parentFolder)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
var combinedFullPath = Path.GetFullPath(Path.Combine(parentFullPath, childFolderName));
if (!combinedFullPath.StartsWith(parentFullPath, StringComparison.OrdinalIgnoreCase))
CancelUnsafeCacheSubfolder(childFolderName, "导致缓存路径越界");
return combinedFullPath;
}
private static void CancelUnsafeCacheSubfolder(string childFolderName, string reason)
{
var message = "远程版本名" + reason + "" + childFolderName;
ModBase.Log("[Download] " + message);
HintService.Hint(message, HintType.Error);
throw new ModBase.CancelledException();
}
#region Minecraft 下载
/// <summary>
/// 下载某个 Minecraft 实例,这会创造一个单独的下载任务,失败会跳过执行并要求反馈。
/// 返回正在下载的任务,若跳过或失败,则返回 Nothing。
/// </summary>
/// <param name="Id">所下载的 Minecraft 的版本名。</param>
/// <param name="JsonUrl">Json 文件的 Mojang 官方地址。</param>
public static ModLoader.LoaderCombo<string> McDownloadClient(NetPreDownloadBehaviour behaviour, string id,
string jsonUrl = null)
{
try
{
var versionFolder = Path.Combine(ModFolder.mcFolderSelected, "versions", id);
// 重复任务检查
foreach (var ongoingLoader in ModLoader.loaderTaskbar.ToList())
{
if (ongoingLoader.name != Lang.Text("Minecraft.Download.Stage.MinecraftDownload", id))
continue;
if (behaviour == NetPreDownloadBehaviour.ExitWhileExistsOrDownloading)
return (ModLoader.LoaderCombo<string>)ongoingLoader;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return (ModLoader.LoaderCombo<string>)ongoingLoader;
}
// 已有实例检查
if (behaviour != NetPreDownloadBehaviour.IgnoreCheck && File.Exists(Path.Combine(versionFolder, id + ".json")) &&
File.Exists(Path.Combine(versionFolder, id + ".jar")))
{
if (behaviour == NetPreDownloadBehaviour.ExitWhileExistsOrDownloading)
return null;
if (ModMain.MyMsgBox(
Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists", id, "\r\n"),
Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists.Title"),
Lang.Text("Common.Action.Continue"), Lang.Text("Common.Action.Cancel")
) == 1)
{
File.Delete(Path.Combine(versionFolder, id + ".jar"));
File.Delete(Path.Combine(versionFolder, id + ".json"));
}
else
{
return null;
}
}
// 启动
var loader =
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.MinecraftDownload", id),
McDownloadClientLoader(id, jsonUrl))
{ OnStateChanged = McInstallState };
loader.Start(versionFolder);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
return loader;
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 Minecraft 下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
return null;
}
}
/// <summary>
/// 保存某个 Minecraft 实例的核心文件(仅 Json 与核心 Jar)。
/// </summary>
/// <param name="id">所下载的 Minecraft 的版本名。</param>
/// <param name="jsonUrl">Json 文件的 Mojang 官方地址。</param>
public static void McDownloadClientCore(string id, string jsonUrl, NetPreDownloadBehaviour behaviour)
{
try
{
var versionFolder = SystemDialogs.SelectFolder();
if (!versionFolder.Contains(@"\"))
return;
versionFolder = Path.Combine(versionFolder, id);
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar)
{
if ((OngoingLoader.name ?? "") != (Lang.Text("Minecraft.Download.Stage.MinecraftDownload", id) ?? ""))
continue;
if (behaviour == NetPreDownloadBehaviour.ExitWhileExistsOrDownloading)
return;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
var loaders = new List<ModLoader.LoaderBase>();
// 下载实例 Json 文件
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadInstanceJson"),
new List<DownloadFile>
{
new(ModDownload.DlSourceLauncherOrMetaGet(jsonUrl), Path.Combine(versionFolder, id + ".json"),
new ModBase.FileChecker(canUseExistsFile: false, isJson: true))
}) { ProgressWeight = 2d });
// 获取支持库文件地址
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeCoreJarUrl"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder)))
{ ProgressWeight = 0.5d, show = false });
// 下载支持库文件
loaders.Add(
new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadCoreJar"), new List<DownloadFile>())
{ ProgressWeight = 5d });
// 启动
var loader =
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.MinecraftDownload", id), loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(id);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 Minecraft 下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
/// <summary>
/// 获取下载某个 Minecraft 版本的加载器列表。
/// 它必须安装到 McFolderSelected,但是可以自定义版本名(不过自定义的实例名不会修改 Json 中的 id 项)。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadClientLoader(string id, string jsonUrl = null,
string instanceName = null)
{
instanceName = instanceName ?? id;
var instanceFolder = Path.Combine(ModFolder.mcFolderSelected, "versions", instanceName);
var loaders = new List<ModLoader.LoaderBase>();
// 下载实例 Json 文件
if (jsonUrl is null)
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.ObtainVanillaJsonUrl"), task =>
{
var jsonAddress = ModDownload.DlClientListGet(id)?.ToString();
task.output = new List<DownloadFile>
{
new(ModDownload.DlSourceLauncherOrMetaGet(jsonAddress), Path.Combine(instanceFolder, instanceName + ".json"))
};
})
{
ProgressWeight = 2d,
show = false
});
loaders.Add(new LoaderDownload(mcDownloadClientJsonName,
new List<DownloadFile>
{
new(ModDownload.DlSourceLauncherOrMetaGet(jsonUrl ?? ""), Path.Combine(instanceFolder, instanceName + ".json"),
new ModBase.FileChecker(canUseExistsFile: false, isJson: true))
}) { ProgressWeight = 3d });
// 下载支持库文件
var loadersLib = new List<ModLoader.LoaderBase>();
loadersLib.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeVanillaLibraries.Side"), task =>
{
var jsonPath = Path.Combine(instanceFolder, instanceName + ".json");
ModBase.WaitForFileReady(jsonPath);
ModBase.Log("[Download] 开始分析原版支持库文件:" + instanceFolder);
if (id == "1.16.5" && Config.Download.FixAuthLib) // 1.16.5 Authlib 修复
try
{
var json = ModBase.ReadFile(jsonPath);
json = json.Replace("2.1.28/authlib-2.1.28.jar", "2.3.31/authlib-2.3.31.jar")
.Replace("com.mojang:authlib:2.1.28", "com.mojang:authlib:2.3.31")
.Replace("ad54da276bf59983d02d5ed16fc14541354c71fd", "bbd00ca33b052f73a6312254780fc580d2da3535")
.Replace("76328", "87662");
ModBase.WriteFile(jsonPath, json);
}
catch (Exception ex)
{
ModBase.Log("[Download] 替换 Authlib 版本失败: " + ex.Message);
}
task.output = ModLibrary.McLibNetFilesFromInstance(new McInstance(instanceFolder));
})
{
ProgressWeight = 1d,
show = false
});
loadersLib.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadVanillaLibraries.Side"),
new List<DownloadFile>())
{ ProgressWeight = 13d, show = false });
loaders.Add(new ModLoader.LoaderCombo<string>(mcDownloadClientLibName, loadersLib)
{ block = false, ProgressWeight = 14d });
// 下载资源文件
var loadersAssets = new List<ModLoader.LoaderBase>();
loadersAssets.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeAssetsIndex.Side"), task =>
{
ModBase.WaitForFileReady(Path.Combine(instanceFolder, instanceName + ".json"));
try
{
var assetIndex = new McInstance(instanceFolder);
task.output = new List<DownloadFile> { ModDownload.DlClientAssetIndexGet(assetIndex) };
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.AssetIndexAnalysisFailed"), ex);
}
// 顺手添加 Json 项目
try
{
var versionJson = (JsonObject)ModBase.GetJson(ModBase.ReadFile(Path.Combine(instanceFolder, instanceName + ".json")));
versionJson.Add("clientVersion", id);
ModBase.WriteFile(Path.Combine(instanceFolder, instanceName + ".json"), versionJson.ToString());
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.AddClientVersionFailed"), ex);
}
})
{
ProgressWeight = 1d,
show = false
});
loadersAssets.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadAssetsIndex.Side"),
new List<DownloadFile>())
{ ProgressWeight = 3d, show = false });
loadersAssets.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeRequiredAssets.Side"), task =>
{
ModLoader.LoaderBase argprogressFeed = task;
task.output =
ModAssets.McAssetsFixList(new McInstance(instanceFolder), true, ref argprogressFeed);
task = (ModLoader.LoaderTask<string, List<DownloadFile>>)argprogressFeed;
})
{
ProgressWeight = 0.01d,
show = false
});
loadersAssets.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadAssets.Side"),
new List<DownloadFile>())
{ ProgressWeight = 14d, show = false });
loaders.Add(
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.DownloadVanillaAssets"),
loadersAssets) { block = false, ProgressWeight = 18d });
return loaders;
}
private static readonly string mcDownloadClientLibName = Lang.Text("Minecraft.Download.Stage.VanillaLibrariesDownload");
private static readonly string mcDownloadClientJsonName = Lang.Text("Minecraft.Download.Stage.VanillaJsonDownload");
#endregion
#region Minecraft 下载菜单
public static MyListItem McDownloadListItem(JsonObject entry, MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
// 确定图标
string logo = entry["type"].ToString() switch
{
"release" => ModBase.pathImage + "Blocks/Grass.png",
"snapshot" => ModBase.pathImage + "Blocks/CommandBlock.png",
"pending" => ModBase.pathImage + "Blocks/CommandBlock.png",
"special" => ModBase.pathImage + "Blocks/GoldBlock.png",
_ => ModBase.pathImage + "Blocks/CobbleStone.png"
};
// 建立控件
var formattedVersion = McFormatter.FormatVersion(entry["id"].ToString()).Replace("_", " ");
var newItem = new MyListItem
{
Logo = logo, SnapsToDevicePixels = true, Title = formattedVersion, Height = 42d,
Type = MyListItem.CheckType.Clickable, Tag = entry
};
if (entry["lore"] is null)
{
if (formattedVersion != (string)entry["id"])
newItem.Info = Lang.Date(entry["releaseTime"].ToObject<DateTime>(), "g") + " | " +
entry["id"];
else
newItem.Info = Lang.Date(entry["releaseTime"].ToObject<DateTime>(), "g");
}
else if (formattedVersion != (string)entry["id"])
{
newItem.Info = entry["lore"] + " | " + entry["id"];
}
else
{
newItem.Info = entry["lore"].ToString();
}
if (entry["url"].ToString().Contains("unlisted-versions-of-minecraft"))
newItem.Tags = Lang.Text("Download.Source.Tag.Uvmc");
newItem.Click += onClick;
// 建立菜单
if (isSaveOnly)
newItem.ContentHandler = McDownloadSaveMenuBuild;
else
newItem.ContentHandler = McDownloadMenuBuild;
// 结束
return newItem;
}
private static void McDownloadSaveMenuBuild(object sender, EventArgs _)
{
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (ss, ee) => McDownloadMenuLog(ss, (dynamic)ee);
var btnServer = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/server", ToolTip = Lang.Text("Download.Version.DownloadServer") };
ToolTipService.SetPlacement(btnServer, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnServer, 30d);
ToolTipService.SetHorizontalOffset(btnServer, 2d);
btnServer.Click += (ss, ee) => McDownloadMenuSaveServer(ss, (dynamic)ee);
((dynamic)sender).Buttons = new[] { btnServer, btnInfo };
}
private static void McDownloadMenuBuild(object sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
btnSave.Click += (a, b) => McDownloadMenuSave(a, (dynamic)b); // dynamic!
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (a, b) => McDownloadMenuLog(a, (dynamic)b); // dynamic!
var btnServer = new MyIconButton { LogoScale = 1d, SvgIcon = "lucide/server", ToolTip = Lang.Text("Download.Version.DownloadServer") };
ToolTipService.SetPlacement(btnServer, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnServer, 30d);
ToolTipService.SetHorizontalOffset(btnServer, 2d);
btnServer.Click += (a, b) => McDownloadMenuSaveServer(a, (dynamic)b); // dynamic!
((dynamic)sender).Buttons = new[] { btnSave, btnInfo, btnServer };
}
private static void McDownloadMenuLog(object sender, RoutedEventArgs e)
{
JsonNode version;
if (((dynamic)sender).Tag is not null)
version = (JsonNode)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
version = (JsonNode)((dynamic)sender).Parent.Tag;
else
version = (JsonNode)((dynamic)sender).Parent.Parent.Tag;
McUpdateLogShow(version);
}
private static void McDownloadMenuSaveServer(object sender, RoutedEventArgs e)
{
MyListItem version;
if (sender is MyListItem)
version = (MyListItem)sender;
else if (((dynamic)sender).Parent is MyListItem)
version = (MyListItem)((dynamic)sender).Parent;
else
version = (MyListItem)((dynamic)sender).Parent.Parent;
try
{
var id = version.Title;
string jsonUrl = ((dynamic)version.Tag)["url"].ToString();
var versionFolder = SystemDialogs.SelectFolder();
if (!versionFolder.Contains(@"\"))
return;
versionFolder = Path.Combine(versionFolder, id);
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.MinecraftServerDownload", id) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.ServerDownloading"), HintType.Error);
return;
}
var loaders = new List<ModLoader.LoaderBase>();
// 下载实例 JSON 文件
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadInstanceJson"),
new List<DownloadFile>
{
new(ModDownload.DlSourceLauncherOrMetaGet(jsonUrl), Path.Combine(versionFolder, id + ".json"),
new ModBase.FileChecker(canUseExistsFile: false, isJson: true))
}) { ProgressWeight = 2d });
// 构建服务端
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.BuildServer"), task =>
{
// 分析服务端 JAR 文件下载地址
var mcInstance = new McInstance(versionFolder);
if (mcInstance.JsonObject["downloads"] is null ||
mcInstance.JsonObject["downloads"]["server"] is null ||
mcInstance.JsonObject["downloads"]["server"]["url"] is null)
{
File.Delete(Path.Combine(versionFolder, id + ".json"));
if (!new DirectoryInfo(versionFolder).GetFileSystemInfos().Any())
Directory.Delete(versionFolder);
task.output = new List<DownloadFile>();
HintService.Hint(Lang.Text("Minecraft.Download.Error.NoOfficialServerDownload", id),
HintType.Error);
Thread.Sleep(2000); // 等玩家把上一个提示看完
task.Abort();
return;
}
var jarUrl = (string)mcInstance.JsonObject["downloads"]["server"]["url"];
var checker = new ModBase.FileChecker(1024L,
(long)(mcInstance.JsonObject["downloads"]["server"]["size"] ?? -1),
(string)mcInstance.JsonObject["downloads"]["server"]["sha1"]);
task.output = new List<DownloadFile>
{ new(ModDownload.DlSourceLauncherOrMetaGet(jarUrl), Path.Combine(versionFolder, id + "-server.jar"), checker) };
// 添加启动脚本
var bat = $"""
@echo off
title {Lang.Text("Minecraft.Download.ServerBatch.Title", id)}
echo {Lang.Text("Minecraft.Download.ServerBatch.InstructionJavaPath")}
echo {Lang.Text("Minecraft.Download.ServerBatch.InstructionPclSettings")}
echo ------------------------------
echo {Lang.Text("Minecraft.Download.ServerBatch.InstructionEula")}
echo ------------------------------
"java" -server -XX:+UseG1GC -Xmx4096M -Xms1024M -XX:+UseCompressedOops -jar {id}-server.jar nogui
echo ----------------------
echo {Lang.Text("Minecraft.Download.ServerBatch.ServerStopped")}
pause
""";
ModBase.WriteFile(Path.Combine(versionFolder, "Launch Server.bat"), bat.Replace("\n", "\r\n"),
encoding: Encoding.Default.Equals(Encoding.UTF8) ? Encoding.UTF8 : Encoding.GetEncoding("GB18030"));
// 删除实例 JSON
File.Delete(Path.Combine(versionFolder, id + ".json"));
})
{
ProgressWeight = 0.5d,
show = false
});
// 下载服务端文件
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadServerFile"), [])
{ ProgressWeight = 5d });
// 启动
var loader =
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.MinecraftServerDownload", id),
loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(id);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 Minecraft 服务端下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
public static void McDownloadMenuSave(object sender, RoutedEventArgs e)
{
var element = (FrameworkElement)sender;
MyListItem version;
if (element is MyListItem s1) version = s1;
else if (element.Parent is MyListItem s2) version = s2;
else version = (MyListItem)((FrameworkElement)element.Parent).Parent;
try
{
var id = version.Title;
var jsonUrl = ((JsonObject)version.Tag)["url"]!.ToString();
var versionFolder = SystemDialogs.SelectFolder();
if (!versionFolder.Contains(@"\"))
return;
versionFolder = Path.Combine(versionFolder, id);
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") != (Lang.Text("Minecraft.Download.Stage.MinecraftDownload", id) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
var loaders = new List<ModLoader.LoaderBase>();
// 下载实例 JSON 文件
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadInstanceJson"),
new List<DownloadFile>
{
new(ModDownload.DlSourceLauncherOrMetaGet(jsonUrl), Path.Combine(versionFolder, id + ".json"),
new ModBase.FileChecker(canUseExistsFile: false, isJson: true))
}) { ProgressWeight = 2d });
// 获取支持库文件地址
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeCoreJarUrl"),
task => task.output = new List<DownloadFile>
{ ModDownload.DlClientJarGet(new McInstance(versionFolder), false) })
{ ProgressWeight = 0.5d, show = false });
// 下载支持库文件
loaders.Add(
new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadCoreJar"), new List<DownloadFile>())
{ ProgressWeight = 5d });
// 启动
var loader =
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.MinecraftDownload", id), loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(id);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 Minecraft 下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
/// <summary>
/// 显示某 Minecraft 版本的更新日志。
/// </summary>
/// <param name="versionJson">在 version_manifest.json 中的对应项。</param>
public static void McUpdateLogShow(JsonNode versionJson)
{
var wikiName = McFormatter.GetWikiUrlSuffix(versionJson["id"].ToString());
var wikiUrl = $"{McFormatter.GetWikiBaseUrl()}/{wikiName.TrimStart('/')}";
ModBase.OpenWebsite(wikiUrl);
}
#endregion
#region OptiFine 下载
public static void McDownloadOptiFine(ModDownload.DlOptiFineListEntry downloadInfo)
{
try
{
var id = downloadInfo.NameVersion;
var versionFolder = Path.Combine(ModFolder.mcFolderSelected, "versions", id);
var isNewVersion = McVersionComparer.CompareVersionGe(downloadInfo.Inherit, "1.14");
var target = isNewVersion
? Path.Combine(ModBase.pathTemp, "Cache", "Code", downloadInfo.NameVersion + "_" + ModBase.GetUuid())
: Path.Combine(ModFolder.mcFolderSelected, "libraries", "optifine", "OptiFine",
downloadInfo.NameFile.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", ""),
downloadInfo.NameFile.Replace("OptiFine_", "OptiFine-").Replace("preview_", ""));
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar)
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.OptiFineDownload", downloadInfo.DisplayName) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 已有实例检查
if (File.Exists(Path.Combine(versionFolder, id + ".json")))
{
if (ModMain.MyMsgBox(
Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists", id, "\r\n"),
Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists.Title"),
Lang.Text("Common.Action.Continue"), Lang.Text("Common.Action.Cancel")
) == 1)
{
File.Delete(Path.Combine(versionFolder, id + ".jar"));
File.Delete(Path.Combine(versionFolder, id + ".json"));
}
else
{
return;
}
}
// 启动
var loader =
new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.OptiFineDownload", downloadInfo.DisplayName),
McDownloadOptiFineLoader(downloadInfo)) { OnStateChanged = McInstallState };
loader.Start(versionFolder);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 OptiFine 下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
private static void McDownloadOptiFineSave(ModDownload.DlOptiFineListEntry downloadInfo)
{
try
{
var id = downloadInfo.NameVersion;
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"), downloadInfo.NameFile, "OptiFine Jar (*.jar)|*.jar");
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.OptiFineDownload", downloadInfo.DisplayName) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
var loader =
new ModLoader.LoaderCombo<ModDownload.DlOptiFineListEntry>(
Lang.Text("Minecraft.Download.Stage.OptiFineDownload", downloadInfo.DisplayName),
McDownloadOptiFineSaveLoader(downloadInfo, target))
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(downloadInfo);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 OptiFine 下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
private static void McDownloadOptiFineInstall(string baseMcFolderHome, string target, ModLoader.LoaderTask<List<DownloadFile>, bool> task, bool useJavaWrapper, Version javaVersion)
{
// 选择 Java
JavaEntry java;
lock (ModJava.javaLock)
{
java = ModJava.JavaSelect(Lang.Text("Minecraft.Download.Error.InstallationCanceled"),
javaVersion, enforceVersionRange: true);
if (java is null)
{
if (!ModJava.JavaDownloadConfirm(Lang.Text("Minecraft.Download.Error.JavaVersionRequired")))
throw new Exception(Lang.Text("Minecraft.Download.Error.JavaNotFoundInstallCanceled"));
// 开始自动下载
var downloadJavaMajor = javaVersion.Major >= 9 ? javaVersion.Major : 8;
var javaLoader = ModJava.GetJavaDownloadLoader();
try
{
javaLoader.Start(downloadJavaMajor, true);
while (javaLoader.State == ModBase.LoadState.Loading && !task.IsAborted)
Thread.Sleep(10);
}
finally
{
javaLoader.Abort(); // 确保取消时中止 Java 下载
}
// 检查下载结果
java = ModJava.JavaSelect(Lang.Text("Minecraft.Download.Error.InstallationCanceled"),
javaVersion, enforceVersionRange: true);
if (task.IsAborted)
return;
if (java is null)
throw new Exception(Lang.Text("Minecraft.Download.Error.JavaNotFoundInstallCanceled"));
}
}
// 添加 Java Wrapper 作为主 Jar
string arguments;
if (useJavaWrapper &&
!(dynamic)Config.Launch.DisableJlw) // dynamic!
arguments =
$"-Doolloo.jlw.tmpdir=\"{ModBase.pathPure.TrimEnd('\\')}\" -Duser.home=\"{baseMcFolderHome.TrimEnd('\\')}\" -cp \"{target}\" -jar \"{ModLaunch.ExtractJavaWrapper()}\" optifine.Installer";
else
arguments = $"-Duser.home=\"{baseMcFolderHome.TrimEnd('\\')}\" -cp \"{target}\" optifine.Installer";
if (java.Installation.MajorVersion >= 9)
arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " + arguments;
// 开始启动
lock (installSyncLock)
{
var info = new ProcessStartInfo
{
FileName = java.Installation.JavaExePath,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardOutput = true,
WorkingDirectory = ModBase.ShortenPath(baseMcFolderHome)
};
if (info.EnvironmentVariables.ContainsKey("appdata"))
info.EnvironmentVariables["appdata"] = baseMcFolderHome;
else
info.EnvironmentVariables.Add("appdata", baseMcFolderHome);
ModBase.Log("[Download] 开始安装 OptiFine" + target);
var totalLength = 0;
var process = new Process { StartInfo = info };
var lastResult = "";
using (var outputWaitHandle = new AutoResetEvent(false))
{
using (var errorWaitHandle = new AutoResetEvent(false))
{
process.OutputDataReceived += (_, e) =>
{
try
{
if (e.Data is null)
{
outputWaitHandle.Set();
}
else
{
lastResult = e.Data;
if (ModBase.modeDebug)
ModBase.Log("[Installer] " + lastResult);
totalLength += 1;
task.Progress += 0.9d / 7000d;
}
}
catch (ObjectDisposedException ex)
{
}
catch (Exception ex)
{
ModBase.Log(ex, "读取 OptiFine 安装器信息失败");
}
try
{
if (task.State == ModBase.LoadState.Aborted && !process.HasExited)
{
ModBase.Log("[Installer] 由于任务取消,已中止 OptiFine 安装");
process.Kill();
}
}
catch
{
}
};
process.ErrorDataReceived += (sender, e) =>
{
try
{
if (e.Data is null)
{
errorWaitHandle.Set();
}
else
{
lastResult = e.Data;
if (ModBase.modeDebug)
ModBase.Log("[Installer] " + lastResult);
totalLength += 1;
task.Progress += 0.9d / 7000d;
}
}
catch (ObjectDisposedException ex)
{
}
catch (Exception ex)
{
ModBase.Log(ex, "读取 OptiFine 安装器错误信息失败");
}
try
{
if (task.State == ModBase.LoadState.Aborted && !process.HasExited)
{
ModBase.Log("[Installer] 由于任务取消,已中止 OptiFine 安装");
process.Kill();
}
}
catch
{
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// 等待
while (!process.HasExited)
Thread.Sleep(10);
// 输出
outputWaitHandle.WaitOne(10000);
errorWaitHandle.WaitOne(10000);
process.Dispose();
if (totalLength < 1000 || lastResult.Contains("at "))
throw new Exception(Lang.Text("Minecraft.Download.Error.InstallerFailedLastLine", lastResult));
}
}
}
}
/// <summary>
/// 获取下载某个 OptiFine 实例的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadOptiFineLoader(ModDownload.DlOptiFineListEntry downloadInfo,
string mcFolder = null, ModLoader.LoaderCombo<string> clientDownloadLoader = null, string clientFolder = null,
bool fixLibrary = true)
{
// 参数初始化
mcFolder = mcFolder ?? ModFolder.mcFolderSelected;
var isCustomFolder = (mcFolder ?? "") != (ModFolder.mcFolderSelected ?? "");
var id = downloadInfo.NameVersion;
var versionFolder = Path.Combine(mcFolder, "versions", id);
var isNewVersion = downloadInfo.Inherit.Contains("w") || McVersionComparer.CompareVersionGe(downloadInfo.Inherit, "1.14");
var target = isNewVersion
? $"{ModMain.RequestTaskTempFolder()}OptiFine.jar"
: $@"{mcFolder}libraries\optifine\OptiFine\{downloadInfo.NameFile.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", "")}\{downloadInfo.NameFile.Replace("OptiFine_", "OptiFine-").Replace("preview_", "")}";
var loaders = new List<ModLoader.LoaderBase>();
var javaVersion = McVersionComparer.CompareVersionGe(downloadInfo.Inherit, "26.1.2")
? new Version(21, 0, 0, 0)
: new Version(1, 8, 0, 0);
// 获取下载地址
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.ObtainOptiFineUrl"), task =>
{
// 启动依赖实例的下载
if (clientDownloadLoader is null)
{
if (isCustomFolder)
throw new Exception(Lang.Text("Minecraft.Download.Error.NoVanillaLoaderSpecified"));
clientDownloadLoader = McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading,
downloadInfo.Inherit);
}
task.Progress = 0.1d;
var sources = new List<string>();
// BMCLAPI 源
var bmclapiInherit = downloadInfo.Inherit;
if (bmclapiInherit == "1.8" || bmclapiInherit == "1.9")
bmclapiInherit += ".0"; // #4281
if (downloadInfo.IsPreview)
sources.Add("https://bmclapi2.bangbang93.com/optifine/" + bmclapiInherit + "/HD_U_" +
downloadInfo.DisplayName.Replace(downloadInfo.Inherit + " ", "").Replace(" ", "/"));
else
sources.Add("https://bmclapi2.bangbang93.com/optifine/" + bmclapiInherit + "/HD_U/" +
downloadInfo.DisplayName.Replace(downloadInfo.Inherit + " ", ""));
// 官方源
string pageData;
try
{
using (var resp = HttpRequest
.Create("https://optifine.net/adloadx?f=" + downloadInfo.NameFile)
.WithHeader("Accept", "text/html")
.WithHeader("Accept-Language", "en-US,en;q=0.5")
.WithHeader("X-Requested-With", "XMLHttpRequest")
.SendAsync()
.GetAwaiter()
.GetResult())
{
resp.EnsureSuccessStatusCode();
pageData = resp.AsString();
}
task.Progress = 0.8d;
sources.Add("https://optifine.net/" + pageData.RegexSearch(@"downloadx\?f=[^""']+")[0]);
ModBase.Log("[Download] OptiFine " + downloadInfo.DisplayName + " 官方下载地址:" + sources.Last());
}
catch (Exception ex)
{
ModBase.Log(ex, "获取 OptiFine " + downloadInfo.DisplayName + " 官方下载地址失败");
}
// 构造文件请求
task.output = new List<DownloadFile>
{ new(sources.ToArray(), target, new ModBase.FileChecker(300 * 1024)) };
})
{
ProgressWeight = 8d
});
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadOptiFineMainFile"), [])
{ ProgressWeight = 8d });
loaders.Add(new ModLoader.LoaderTask<List<DownloadFile>, bool>(
Lang.Text("Minecraft.Download.Stage.WaitVanillaDownload"), task =>
{
// 等待原版文件下载完成
if (clientDownloadLoader is null)
return;
var targetLoaders = clientDownloadLoader.GetLoaderList()
.Where(l => (l.name ?? "") == mcDownloadClientLibName || (l.name ?? "") == mcDownloadClientJsonName)
.Where(l => l.State != ModBase.LoadState.Finished).ToList();
if (targetLoaders.Any())
ModBase.Log("[Download] OptiFine 安装正在等待原版文件下载完成");
while (targetLoaders.Any() && !task.IsAborted)
{
targetLoaders = targetLoaders.Where(l => l.State != ModBase.LoadState.Finished).ToList();
Thread.Sleep(50);
}
if (task.IsAborted)
return;
// 拷贝原版文件
if (!isCustomFolder)
return;
lock (vanillaSyncLock)
{
var clientName = ModBase.GetFolderNameFromPath(clientFolder);
Directory.CreateDirectory(Path.Combine(mcFolder, "versions", downloadInfo.Inherit));
if (!File.Exists(Path.Combine(mcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".json")))
ModBase.CopyFile($"{clientFolder}{clientName}.json",
$@"{mcFolder}versions\{downloadInfo.Inherit}\{downloadInfo.Inherit}.json");
if (!File.Exists(Path.Combine(mcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".jar")))
ModBase.CopyFile($"{clientFolder}{clientName}.jar",
$@"{mcFolder}versions\{downloadInfo.Inherit}\{downloadInfo.Inherit}.jar");
}
})
{
ProgressWeight = 0.1d,
show = false
});
// 安装(新旧方式均需要原版 Jar 和 Json)
if (isNewVersion)
{
ModBase.Log("[Download] 检测为新版 OptiFine" + downloadInfo.Inherit);
loaders.Add(new ModLoader.LoaderTask<List<DownloadFile>, bool>(
Lang.Text("Minecraft.Download.Stage.InstallOptiFine.MethodA"), task =>
{
var baseMcFolderHome = ModMain.RequestTaskTempFolder();
var baseMcFolder = Path.Combine(baseMcFolderHome, ".minecraft");
try
{
// 准备安装环境
if (Directory.Exists(Path.Combine(baseMcFolder, "versions", downloadInfo.Inherit)))
ModBase.DeleteDirectory(Path.Combine(baseMcFolder, "versions", downloadInfo.Inherit));
Directory.CreateDirectory(Path.Combine(baseMcFolder, "versions", downloadInfo.Inherit));
ModFolder.McFolderLauncherProfilesJsonCreate(baseMcFolder);
ModBase.CopyFile(
Path.Combine(mcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".json"),
Path.Combine(baseMcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".json"));
ModBase.CopyFile(
Path.Combine(mcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".jar"),
Path.Combine(baseMcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".jar"));
task.Progress = 0.06d;
// 进行安装
var useJavaWrapper = ModBase.IsUtf8CodePage();
Retry: ;
try
{
McDownloadOptiFineInstall(baseMcFolderHome, target, task, useJavaWrapper, javaVersion);
}
catch (Exception ex)
{
if (!useJavaWrapper)
{
ModBase.Log(ex, "不使用 JavaWrapper 安装 OptiFine 失败,将使用 JavaWrapper 并重试");
useJavaWrapper = true;
goto Retry;
}
throw new Exception(Lang.Text("Minecraft.Download.Error.OptiFineInstallerRunFailed"), ex);
}
task.Progress = 0.96d;
// 复制文件
File.Delete(Path.Combine(baseMcFolder, "launcher_profiles.json"));
ModBase.CopyDirectory(baseMcFolder, mcFolder);
task.Progress = 0.98d;
// 清理文件
File.Delete(target);
ModBase.DeleteDirectory(baseMcFolderHome);
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.OptiFineInstallFailed.MethodA"), ex);
}
})
{
ProgressWeight = 8d
});
}
else
{
ModBase.Log("[Download] 检测为旧版 OptiFine" + downloadInfo.Inherit);
// 新建实例文件夹
// 复制 Jar 文件
// 建立 Json 文件
loaders.Add(new ModLoader.LoaderTask<List<DownloadFile>, bool>(
Lang.Text("Minecraft.Download.Stage.InstallOptiFine.MethodB"), task =>
{
try
{
Directory.CreateDirectory(versionFolder);
task.Progress = 0.1d;
if (File.Exists(Path.Combine(versionFolder, id + ".jar"))) File.Delete(Path.Combine(versionFolder, id + ".jar"));
ModBase.CopyFile(
Path.Combine(mcFolder, "versions", downloadInfo.Inherit, downloadInfo.Inherit + ".jar"),
Path.Combine(versionFolder, id + ".jar"));
task.Progress = 0.7d;
var inheritInstance =
new McInstance(Path.Combine(mcFolder, "versions", downloadInfo.Inherit));
var json = @"{
""id"": """ + id + @""",
""inheritsFrom"": """ + downloadInfo.Inherit + @""",
""time"": """ +
(string.IsNullOrEmpty(downloadInfo.ReleaseTime)
? inheritInstance.releaseTime.ToString("yyyy'-'MM'-'dd", CultureInfo.InvariantCulture)
: downloadInfo.ReleaseTime.Replace("/", "-")) + @"T23:33:33+08:00"",
""releaseTime"": """ +
(string.IsNullOrEmpty(downloadInfo.ReleaseTime)
? inheritInstance.releaseTime.ToString("yyyy'-'MM'-'dd", CultureInfo.InvariantCulture)
: downloadInfo.ReleaseTime.Replace("/", "-")) + @"T23:33:33+08:00"",
""type"": ""release"",
""libraries"": [
{""name"": ""optifine:OptiFine:" +
downloadInfo.NameFile.Replace("OptiFine_", "").Replace(".jar", "")
.Replace("preview_", "") + // 输出旧版 Json 格式
@"""},
{""name"": ""net.minecraft:launchwrapper:1.12""}
],
""mainClass"": ""net.minecraft.launchwrapper.Launch"",";
task.Progress = 0.8d;
if (inheritInstance.IsOldJson)
json += @"
""minimumLauncherVersion"": 18,
""minecraftArguments"": """ + inheritInstance.JsonObject["minecraftArguments"] + // 输出新版 Json 格式
@" --tweakClass optifine.OptiFineTweaker""
}";
else
json += @"
""minimumLauncherVersion"": ""21"",
""arguments"": {
""game"": [
""--tweakClass"",
""optifine.OptiFineTweaker""
]
}
}";
ModBase.WriteFile(Path.Combine(versionFolder, id + ".json"), json);
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.OptiFineInstallFailed.MethodB"), ex);
}
})
{ ProgressWeight = 1d });
}
// 下载支持库
if (fixLibrary)
{
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeOptiFineLibraries"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder)))
{ ProgressWeight = 1d, show = false });
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadOptiFineLibraries"),
new List<DownloadFile>())
{ ProgressWeight = 4d });
}
return loaders;
}
/// <summary>
/// 获取保存某个 OptiFine 版本的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadOptiFineSaveLoader(ModDownload.DlOptiFineListEntry downloadInfo,
string targetFolder)
{
var loaders = new List<ModLoader.LoaderBase>();
// 获取下载地址
loaders.Add(new ModLoader.LoaderTask<ModDownload.DlOptiFineListEntry, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.ObtainOptiFineDownloadUrl"),
task =>
{
var sources = new List<string>();
// BMCLAPI 源
var bmclapiInherit = downloadInfo.Inherit;
if (bmclapiInherit == "1.8" || bmclapiInherit == "1.9")
bmclapiInherit += ".0"; // #4281
if (downloadInfo.IsPreview)
sources.Add("https://bmclapi2.bangbang93.com/optifine/" + bmclapiInherit + "/HD_U_" +
downloadInfo.DisplayName.Replace(downloadInfo.Inherit + " ", "").Replace(" ", "/"));
else
sources.Add("https://bmclapi2.bangbang93.com/optifine/" + bmclapiInherit + "/HD_U/" +
downloadInfo.DisplayName.Replace(downloadInfo.Inherit + " ", ""));
// 官方源
string pageData;
try
{
using (var resp = HttpRequest
.Create("https://optifine.net/adloadx?f=" + downloadInfo.NameFile)
.WithHeader("Accept", "text/html")
.WithHeader("Accept-Language", "en-US,en;q=0.5")
.WithHeader("X-Requested-With", "XMLHttpRequest")
.SendAsync().GetAwaiter().GetResult())
{
resp.EnsureSuccessStatusCode();
pageData = resp.AsString();
}
task.Progress = 0.8d;
sources.Add("https://optifine.net/" + pageData.RegexSearch(@"downloadx\?f=[^""']+")[0]);
ModBase.Log("[Download] OptiFine " + downloadInfo.DisplayName + " 官方下载地址:" + sources.Last());
}
catch (Exception ex)
{
ModBase.Log(ex, "获取 OptiFine " + downloadInfo.DisplayName + " 官方下载地址失败");
}
task.Progress = 0.9d;
// 构造文件请求
task.output = new List<DownloadFile>
{ new(sources.ToArray(), targetFolder, new ModBase.FileChecker(64 * 1024)) };
})
{
ProgressWeight = 6d
});
// 下载
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadOptiFineMainFile"),
new List<DownloadFile>())
{ ProgressWeight = 10d, block = true });
return loaders;
}
#endregion
#region OptiFine 下载菜单
public static MyListItem OptiFineDownloadListItem(ModDownload.DlOptiFineListEntry entry,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
var infoParts = new List<string>
{
entry.IsPreview
? Lang.Text("Download.Version.Type.Preview")
: Lang.Text("Download.Version.Type.Release")
};
if (!string.IsNullOrEmpty(entry.ReleaseTime))
infoParts.Add(Lang.Text("Download.Version.ReleaseDate", entry.ReleaseTime));
if (entry.RequiredForgeVersion is null)
infoParts.Add(Lang.Text("Download.Version.Optifine.IncompatibleForge"));
else if (!string.IsNullOrEmpty(entry.RequiredForgeVersion))
infoParts.Add(Lang.Text("Download.Version.Optifine.CompatibleForge", entry.RequiredForgeVersion));
var newItem = new MyListItem
{
Title = entry.DisplayName,
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = string.Join(" | ", infoParts),
Logo = ModBase.pathImage + "Blocks/GrassPath.png"
};
newItem.Click += onClick;
// 建立菜单
newItem.ContentHandler = isSaveOnly
? OptiFineSaveContMenuBuild
: OptiFineContMenuBuild;
// 结束
return newItem;
}
private static void OptiFineSaveContMenuBuild(object sender, EventArgs e)
{
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (sender, e) => OptiFineLog_Click(sender, (RoutedEventArgs)e);
((dynamic)sender).Buttons = new[] { btnInfo };
}
private static void OptiFineContMenuBuild(object sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
//btnSave.Click += () ModDownloadLib.OptiFineSave_Click;
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (sender, e) => OptiFineLog_Click(sender, (RoutedEventArgs)e);
((dynamic)sender).Buttons = new[] { btnSave, btnInfo };
}
private static void OptiFineLog_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlOptiFineListEntry version;
if (((dynamic)sender).Tag is not null)
version = (ModDownload.DlOptiFineListEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
version = (ModDownload.DlOptiFineListEntry)((dynamic)sender).Parent.Tag;
else
version = (ModDownload.DlOptiFineListEntry)((dynamic)sender).Parent.Parent.Tag;
ModBase.OpenWebsite("https://optifine.net/changelog?f=" + version.NameFile);
}
public static void OptiFineSave_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlOptiFineListEntry version;
if (((dynamic)sender).Tag is not null)
version = (ModDownload.DlOptiFineListEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
version = (ModDownload.DlOptiFineListEntry)((dynamic)sender).Parent.Tag;
else
version = (ModDownload.DlOptiFineListEntry)((dynamic)sender).Parent.Parent.Tag;
McDownloadOptiFineSave(version);
}
#endregion
#region LiteLoader 下载
public static void McDownloadLiteLoader(ModDownload.DlLiteLoaderListEntry downloadInfo)
{
try
{
var id = downloadInfo.Inherit;
var target = Path.Combine(ModBase.pathTemp, "Download", id + "-Liteloader.jar");
var versionName = downloadInfo.Inherit + "-LiteLoader";
var versionFolder = Path.Combine(ModFolder.mcFolderSelected, "versions", versionName);
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") != (Lang.Text("Minecraft.Download.Stage.LiteLoaderDownload", id) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 已有实例检查
if (File.Exists(Path.Combine(versionFolder, versionName + ".json")))
{
if (ModMain.MyMsgBox(
Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists", versionName, "\r\n"),
Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists.Title"),
Lang.Text("Common.Action.Continue"), Lang.Text("Common.Action.Cancel")
) == 1)
{
File.Delete(Path.Combine(versionFolder, versionName + ".jar"));
File.Delete(Path.Combine(versionFolder, versionName + ".json"));
}
else
{
return;
}
}
// 启动
var loader =
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.LiteLoaderDownload", id),
McDownloadLiteLoaderLoader(downloadInfo))
{ OnStateChanged = McInstallState };
loader.Start(versionFolder);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 LiteLoader 下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
private static void McDownloadLiteLoaderSave(ModDownload.DlLiteLoaderListEntry downloadInfo)
{
try
{
var id = downloadInfo.Inherit;
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"), downloadInfo.FileName.Replace("-SNAPSHOT", ""),
Lang.Text("Minecraft.Download.Stage.ForgelikeInstallerFilter", "LiteLoader", "jar"));
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") != (Lang.Text("Minecraft.Download.Stage.LiteLoaderDownload", id) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
// 下载
var address = new List<string>();
if (downloadInfo.IsLegacy)
// 老版本
switch (downloadInfo.Inherit ?? "")
{
case "1.7.10":
{
address.Add("https://dl.liteloader.com/redist/1.7.10/liteloader-installer-1.7.10-04.jar");
break;
}
case "1.7.2":
{
address.Add("https://dl.liteloader.com/redist/1.7.2/liteloader-installer-1.7.2-04.jar");
break;
}
case "1.6.4":
{
address.Add("https://dl.liteloader.com/redist/1.6.4/liteloader-installer-1.6.4-01.jar");
break;
}
case "1.6.2":
{
address.Add("https://dl.liteloader.com/redist/1.6.2/liteloader-installer-1.6.2-04.jar");
break;
}
case "1.5.2":
{
address.Add("https://dl.liteloader.com/redist/1.5.2/liteloader-installer-1.5.2-01.jar");
break;
}
default:
{
throw new NotSupportedException(Lang.Text("Minecraft.Download.Error.UnknownMinecraftVersion",
downloadInfo.Inherit));
}
}
else
// 官方源
address.Add("http://jenkins.liteloader.com/job/LiteLoaderInstaller%20" + downloadInfo.Inherit +
"/lastSuccessfulBuild/artifact/" +
(downloadInfo.Inherit == "1.8" ? "ant/dist/" : "build/libs/") + downloadInfo.FileName);
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadMainFile"),
new List<DownloadFile> { new(address.ToArray(), target, new ModBase.FileChecker(1024 * 1024)) })
{ ProgressWeight = 15d });
// 启动
var loader =
new ModLoader.LoaderCombo<ModDownload.DlLiteLoaderListEntry>(
Lang.Text("Minecraft.Download.Stage.LiteLoaderInstallerDownload", id), loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(downloadInfo);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 LiteLoader 安装器下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
/// <summary>
/// 获取下载某个 LiteLoader 实例的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadLiteLoaderLoader(ModDownload.DlLiteLoaderListEntry downloadInfo,
string mcFolder = null, ModLoader.LoaderCombo<string> clientDownloadLoader = null, bool fixLibrary = true)
{
// 参数初始化
mcFolder = mcFolder ?? ModFolder.mcFolderSelected;
var isCustomFolder = (mcFolder ?? "") != (ModFolder.mcFolderSelected ?? "");
var id = downloadInfo.Inherit;
var target = Path.Combine(ModBase.pathTemp, "Download", id + "-Liteloader.jar");
var versionName = downloadInfo.Inherit + "-LiteLoader";
var versionFolder = Path.Combine(mcFolder, "versions", versionName);
var loaders = new List<ModLoader.LoaderBase>();
// 启动依赖实例的下载
if (clientDownloadLoader is null)
loaders.Add(new ModLoader.LoaderTask<string, string>(
Lang.Text("Minecraft.Download.Stage.StartLiteLoaderDependencyDownload"), _ =>
{
if (isCustomFolder)
throw new Exception(Lang.Text("Minecraft.Download.Error.NoVanillaLoaderSpecified"));
clientDownloadLoader = McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading,
downloadInfo.Inherit);
})
{
ProgressWeight = 0.2d,
show = false,
block = false
});
// 安装
// 新建实例文件夹
// 构造实例 Json
// 输出 Json 文件
loaders.Add(new ModLoader.LoaderTask<string, string>(Lang.Text("Minecraft.Download.Stage.InstallLiteLoader"),
_ =>
{
try
{
Directory.CreateDirectory(versionFolder);
var versionJson = new JsonObject();
versionJson.Add("id", versionName);
var releaseDate = DateTime.Parse(downloadInfo.ReleaseTime, Lang.Culture);
versionJson.Add("time", releaseDate);
versionJson.Add("releaseTime", releaseDate);
versionJson.Add("type", "release");
versionJson.Add("arguments",
(JsonNode)ModBase.GetJson("{\"game\":[\"--tweakClass\",\"" + downloadInfo.jsonToken["tweakClass"] +
"\"]}"));
versionJson.Add("libraries", downloadInfo.jsonToken["libraries"]?.DeepClone());
versionJson["libraries"].AsArray().Add(ModBase.GetJson("{\"name\": \"com.mumfrey:liteloader:" +
downloadInfo.jsonToken["version"] +
"\",\"url\": \"https://dl.liteloader.com/versions/\"}"));
versionJson.Add("mainClass", "net.minecraft.launchwrapper.Launch");
versionJson.Add("minimumLauncherVersion", 18);
versionJson.Add("inheritsFrom", downloadInfo.Inherit);
versionJson.Add("jar", downloadInfo.Inherit);
ModBase.WriteFile(Path.Combine(versionFolder, versionName + ".json"), versionJson.ToString());
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.LiteLoaderInstallFailed"), ex);
}
}) { ProgressWeight = 1d });
// 下载支持库
if (fixLibrary)
{
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeLiteLoaderLibraries"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder)))
{ ProgressWeight = 1d, show = false });
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLiteLoaderLibraries"),
new List<DownloadFile>())
{ ProgressWeight = 6d });
}
return loaders;
}
#endregion
#region LiteLoader 下载菜单
public static MyListItem LiteLoaderDownloadListItem(ModDownload.DlLiteLoaderListEntry entry,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
var infoParts = new List<string>
{
entry.IsPreview
? Lang.Text("Download.Version.Type.Preview")
: Lang.Text("Download.Version.Type.Stable")
};
if (!string.IsNullOrEmpty(entry.ReleaseTime))
infoParts.Add(Lang.Text("Download.Version.ReleaseDate", entry.ReleaseTime));
// 建立控件
var newItem = new MyListItem
{
Title = entry.Inherit,
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = string.Join(" | ", infoParts),
Logo = ModBase.pathImage + "Blocks/Egg.png"
};
newItem.Click += onClick;
// 建立菜单
newItem.ContentHandler = isSaveOnly
? LiteLoaderSaveContMenuBuild
: LiteLoaderContMenuBuild;
// 结束
return newItem;
}
private static void LiteLoaderSaveContMenuBuild(MyListItem sender, EventArgs e)
{
if ((bool)((dynamic)sender.Tag).IsLegacy)
{
sender.Buttons = Array.Empty<MyIconButton>();
}
else
{
var btnList = new MyIconButton { SvgIcon = "lucide/list", ToolTip = Lang.Text("Download.Version.ViewAllVersions"), Tag = sender };
ToolTipService.SetPlacement(btnList, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnList, 30d);
ToolTipService.SetHorizontalOffset(btnList, 2d);
btnList.Click += (sender, e) => LiteLoaderAll_Click(sender, (RoutedEventArgs)e);
sender.Buttons = new[] { btnList };
}
}
private static void LiteLoaderContMenuBuild(MyListItem sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveInstaller"), Tag = sender };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
btnSave.Click += (sender, e) => LiteLoaderSave_Click(sender, (RoutedEventArgs)e);
if ((bool)((dynamic)sender.Tag).IsLegacy)
{
sender.Buttons = [btnSave];
}
else
{
var btnList = new MyIconButton { SvgIcon = "lucide/list", ToolTip = Lang.Text("Download.Version.ViewAllVersions"), Tag = sender };
ToolTipService.SetPlacement(btnList, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnList, 30d);
ToolTipService.SetHorizontalOffset(btnList, 2d);
btnList.Click += (sender, e) => LiteLoaderAll_Click(sender, (RoutedEventArgs)e);
sender.Buttons = [btnSave, btnList];
}
}
private static void LiteLoaderAll_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlLiteLoaderListEntry version;
if (((dynamic)sender).Tag is ModDownload.DlLiteLoaderListEntry)
version = (ModDownload.DlLiteLoaderListEntry)((dynamic)sender).Tag;
else
version = (ModDownload.DlLiteLoaderListEntry)((dynamic)sender).Tag.Tag;
ModBase.OpenWebsite("https://jenkins.liteloader.com/view/" + version.Inherit);
}
public static void LiteLoaderSave_Click(object sender, RoutedEventArgs e)
{
// ListItem 与小按钮都会调用这个方法
ModDownload.DlLiteLoaderListEntry version;
if (((dynamic)sender).Tag is ModDownload.DlLiteLoaderListEntry)
version = (ModDownload.DlLiteLoaderListEntry)((dynamic)sender).Tag;
else
version = (ModDownload.DlLiteLoaderListEntry)((dynamic)sender).Tag.Tag;
McDownloadLiteLoaderSave(version);
}
#endregion
#region Forgelike 下载
public static void McDownloadForgelikeSave(ModDownload.DlForgelikeEntry info)
{
try
{
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"),
$"{info.LoaderName}-{info.Inherit}-{info.VersionName}.{info.FileExtension}",
Lang.Text("Minecraft.Download.Stage.ForgelikeInstallerFilter", info.LoaderName, info.FileExtension));
var displayName = $"{info.LoaderName} {info.Inherit} - {info.VersionName}";
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.ForgelikeDownload", displayName) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 获取下载地址
var files = new List<DownloadFile>();
if (info.forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge)
{
// NeoForge
var neo = (ModDownload.DlNeoForgeListEntry)info;
var url = neo.UrlBase + "-installer.jar";
files.Add(new DownloadFile(
new[] { url.Replace("maven.neoforged.net/releases", "bmclapi2.bangbang93.com/maven"), url }, target,
new ModBase.FileChecker(64 * 1024)));
}
else if (info.forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Cleanroom)
{
// Cleanroom
var clr = (ModDownload.DlCleanroomListEntry)info;
var url = clr.UrlBase + "-installer.jar";
files.Add(new DownloadFile(new[] { url }, target, new ModBase.FileChecker(64 * 1024)));
}
else
{
// Forge
var forge = (ModDownload.DlForgeVersionEntry)info;
files.Add(new DownloadFile(
new[]
{
$"https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/{forge.Inherit}-{forge.FileVersion}/forge-{forge.Inherit}-{forge.FileVersion}-{forge.Category}.{forge.FileExtension}",
$"https://files.minecraftforge.net/maven/net/minecraftforge/forge/{forge.Inherit}-{forge.FileVersion}/forge-{forge.Inherit}-{forge.FileVersion}-{forge.Category}.{forge.FileExtension}"
}, target, new ModBase.FileChecker(64 * 1024, hash: forge.Hash)));
}
// 构造加载器
var loaders = new List<ModLoader.LoaderBase>();
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadMainFile"), files)
{ ProgressWeight = 6d });
// 启动
var loader =
new ModLoader.LoaderCombo<ModDownload.DlForgelikeEntry>(
Lang.Text("Minecraft.Download.Stage.ForgelikeDownload", displayName), loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(info);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
$"开始 {info.LoaderName} 安装器下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
private static void ForgelikeInjector(string target, ModLoader.LoaderTask<bool, bool> task, string mcFolder,
bool useJavaWrapper, ModDownload.DlForgelikeEntry.ForgelikeType forgeType)
{
// 选择 Java
JavaEntry java;
lock (ModJava.javaLock)
{
java = ModJava.JavaSelect(Lang.Text("Minecraft.Download.Error.InstallationCanceled"),
new Version(1, 8, 0, 60));
if (java is null)
{
if (!ModJava.JavaDownloadConfirm(Lang.Text("Minecraft.Download.Error.JavaVersionRequired")))
throw new Exception(Lang.Text("Minecraft.Download.Error.JavaNotFoundInstallCanceled"));
// 开始自动下载
var javaLoader = ModJava.GetJavaDownloadLoader();
try
{
javaLoader.Start(17, true);
while (javaLoader.State == ModBase.LoadState.Loading && !task.IsAborted)
Thread.Sleep(10);
}
finally
{
javaLoader.Abort(); // 确保取消时中止 Java 下载
}
// 检查下载结果
java = ModJava.JavaSelect(Lang.Text("Minecraft.Download.Error.InstallationCanceled"),
new Version(1, 8, 0, 60));
if (task.IsAborted)
return;
if (java is null)
throw new Exception(Lang.Text("Minecraft.Download.Error.JavaNotFoundInstallCanceled"));
}
}
// 添加 Java Wrapper 作为主 Jar
string arguments;
if (useJavaWrapper && !Config.Launch.DisableJlw)
arguments =
$@"-Doolloo.jlw.tmpdir=""{ModBase.pathPure.TrimEnd('\\')}"" -cp ""{ModBase.pathTemp}Cache\forge_installer.jar;{target}"" -jar ""{ModLaunch.ExtractJavaWrapper()}"" com.bangbang93.ForgeInstaller ""{mcFolder}";
else
arguments =
$@"-cp ""{ModBase.pathTemp}Cache\forge_installer.jar;{target}"" com.bangbang93.ForgeInstaller ""{mcFolder}";
if (java.Installation.MajorVersion >= 9)
arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " + arguments;
// 开始启动
lock (installSyncLock)
{
var info = new ProcessStartInfo
{
FileName = java.Installation.JavaExePath,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardOutput = true
};
string loaderName = ModBase.GetStringFromEnum(forgeType);
ModBase.Log($"[Download] 开始安装 {loaderName}" + arguments);
var process = new Process { StartInfo = info };
var lastResults = new Queue<string>();
using (var outputWaitHandle = new AutoResetEvent(false))
{
using (var errorWaitHandle = new AutoResetEvent(false))
{
process.OutputDataReceived += (sender, e) =>
{
try
{
if (e.Data is null)
{
outputWaitHandle.Set();
}
else
{
lastResults.Enqueue(e.Data);
if (lastResults.Count > 100)
lastResults.Dequeue();
ForgelikeInjectorLine(e.Data, task);
}
}
catch (ObjectDisposedException ex)
{
}
catch (Exception ex)
{
ModBase.Log(ex, $"读取 {loaderName} 安装器信息失败");
}
try
{
if (task.State == ModBase.LoadState.Aborted && !process.HasExited)
{
ModBase.Log($"[Installer] 由于任务取消,已中止 {loaderName} 安装");
process.Kill();
}
}
catch
{
}
};
process.ErrorDataReceived += (sender, e) =>
{
try
{
if (e.Data is null)
{
errorWaitHandle.Set();
}
else
{
lastResults.Enqueue(e.Data);
if (lastResults.Count > 100)
lastResults.Dequeue();
ForgelikeInjectorLine(e.Data, task);
}
}
catch (ObjectDisposedException ex)
{
}
catch (Exception ex)
{
ModBase.Log(ex, $"读取 {loaderName} 安装器错误信息失败");
}
try
{
if (task.State == ModBase.LoadState.Aborted && !process.HasExited)
{
ModBase.Log($"[Installer] 由于任务取消,已中止 {loaderName} 安装");
process.Kill();
}
}
catch
{
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// 等待
while (!process.HasExited)
Thread.Sleep(10);
// 输出
outputWaitHandle.WaitOne(10000);
errorWaitHandle.WaitOne(10000);
process.Dispose();
// 检查是否安装成功:最后 5 行中是否有 true(true 可能在倒数数行,见 #832)
if (lastResults.Reverse().Take(5).Any(l => l == "true"))
return;
ModBase.Log(lastResults.Join("\r\n"));
var lastLines = "";
for (int i = Math.Max(0, lastResults.Count - 5), loopTo = lastResults.Count - 1;
i <= loopTo;
i++) // 最后 5 行
lastLines += "\r\n" + lastResults.ElementAtOrDefault(i);
throw new Exception(Lang.Text("Minecraft.Download.Error.LoaderInstallerFailedLastLine", loaderName,
lastLines));
}
}
}
}
private static void ForgelikeInjectorLine(string content, ModLoader.LoaderTask<bool, bool> task)
{
switch (content ?? "")
{
case "Extracting json":
{
ModBase.Log("[Installer] " + content);
task.Progress = 0.07d;
break;
}
case "Downloading libraries":
{
ModBase.Log("[Installer] " + content);
task.Progress = 0.08d;
break;
}
case " File exists: Checksum validated.":
{
if (ModBase.modeDebug)
ModBase.Log("[Installer] " + content);
task.Progress += 0.003d;
break;
}
case "Building Processors":
{
task.Progress = 0.18d;
break;
}
case "Task: DOWNLOAD_MOJMAPS": // B
{
task.Progress = 0.2d;
break;
}
case "Task: MERGE_MAPPING": // B
{
task.Progress = 0.3d;
break;
}
case "Splitting: ":
{
task.Progress = 0.35d;
break;
}
case "Parameter Annotations": // B
{
task.Progress = 0.4d;
break;
}
case "Processing Complete": // B
{
task.Progress = 0.5d;
break;
}
case "log: null": // new
{
task.Progress = 0.5d;
break;
}
case "Sorting": // new
{
task.Progress = 0.65d;
break;
}
case "Remapping final jar": // A
{
task.Progress = 0.72d;
break;
}
case "Remapping jar... 50%": // A
{
task.Progress = 0.76d;
break;
}
case "Remapping jar... 100%": // A
{
task.Progress = 0.81d;
break;
}
case "Injecting profile":
{
task.Progress = 0.91d;
break;
}
default:
{
if (ModBase.modeDebug)
ModBase.Log("[Installer] " + content);
return;
}
}
ModBase.Log("[Installer] " + content);
}
/// <summary>
/// 获取下载某个 Forgelike 实例的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadForgelikeLoader(ModDownload.DlForgelikeEntry.ForgelikeType forgeType, string loaderVersion,
string targetVersion, string inherit, ModDownload.DlForgelikeEntry info = null, string mcFolder = null, ModLoader.LoaderCombo<string> clientDownloadLoader = null, string clientFolder = null)
{
// 参数初始化
mcFolder = mcFolder ?? ModFolder.mcFolderSelected;
if (forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge && info is null)
{
// 需要传入 API Name,但整合包版本可能不以 1.20.1- 开头,所以需要进行特别处理
if (inherit == "1.20.1" && !loaderVersion.StartsWithF("1.20.1-"))
info = new ModDownload.DlNeoForgeListEntry("1.20.1-" + loaderVersion);
else
info = new ModDownload.DlNeoForgeListEntry(loaderVersion);
}
if (forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Cleanroom && info is null) info = new ModDownload.DlCleanroomListEntry(loaderVersion);
if (forgeType != ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge && loaderVersion.StartsWithF("1.") && loaderVersion.Contains("-"))
{
// 类似 1.19.3-41.2.8 格式,优先使用 Version 中要求的版本而非 Inherit(例如 1.19.3 却使用了 1.19 的 Forge
inherit = loaderVersion.BeforeFirst("-");
loaderVersion = loaderVersion.AfterLast("-");
}
string loaderName = ModBase.GetStringFromEnum(forgeType);
var isCustomFolder = (mcFolder ?? "") != (ModFolder.mcFolderSelected ?? "");
var installerAddress = ModMain.RequestTaskTempFolder() + "forge_installer.jar";
var versionFolder = $@"{mcFolder}versions\{targetVersion}\";
var displayName = $"{loaderName} {inherit} - {loaderVersion}";
var loaders = new List<ModLoader.LoaderBase>();
var libVersionFolder = $@"{ModFolder.mcFolderSelected}versions\{targetVersion}\"; // 作为 Lib 文件目标的实例文件夹
// 获取 Forge 下载信息
if (info is null)
loaders.Add(new ModLoader.LoaderTask<string, string>(
Lang.Text("Minecraft.Download.Stage.ObtainLoaderDetails", loaderName), task =>
{
// 获取 Forge 对应 MC 版本列表
var forgeLoader =
new ModLoader.LoaderTask<string, List<ModDownload.DlForgeVersionEntry>>(
"McDownloadForgeLoader " + inherit, ModDownload.DlForgeVersionMain);
forgeLoader.WaitForExit(inherit);
task.Progress = 0.8d;
// 查找对应版本
foreach (var ForgeVersion in forgeLoader.output)
if (McVersionComparer.CompareVersion(ForgeVersion.version.ToString(), loaderVersion) == 0)
{
info = ForgeVersion;
return;
}
throw new Exception(Lang.Text("Minecraft.Download.Error.LoaderDetailsNotFound", loaderName, inherit,
loaderVersion));
})
{
ProgressWeight = 3d
});
// 下载 Forgelike 主文件
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.PrepareLoaderDownload", loaderName), task =>
{
// 启动依赖实例的下载
if (clientDownloadLoader is null)
{
if (isCustomFolder)
throw new Exception(Lang.Text("Minecraft.Download.Error.NoVanillaLoaderSpecified"));
clientDownloadLoader =
McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, inherit);
}
// 添加主文件下载
var files = new List<DownloadFile>();
if (info.forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge)
{
// NeoForge
var neo = (ModDownload.DlNeoForgeListEntry)info;
var url = neo.UrlBase + "-installer.jar";
files.Add(new DownloadFile(
new[] { url.Replace("maven.neoforged.net/releases", "bmclapi2.bangbang93.com/maven"), url },
installerAddress, new ModBase.FileChecker(64 * 1024)));
}
else if (info.forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Cleanroom)
{
// Cleanroom
var clr = (ModDownload.DlCleanroomListEntry)info;
var url = clr.UrlBase + "-installer.jar";
files.Add(new DownloadFile(new[] { url }, installerAddress, new ModBase.FileChecker(64 * 1024)));
}
else
{
// Forge
var forge = (ModDownload.DlForgeVersionEntry)info;
var fileName =
$"{forge.Inherit.Replace("-", "_")}-{forge.FileVersion}/forge-{forge.Inherit.Replace("-", "_")}-{forge.FileVersion}-{forge.Category}.{forge.FileExtension}";
files.Add(new DownloadFile(
new[]
{
$"https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/{fileName}",
$"https://files.minecraftforge.net/maven/net/minecraftforge/forge/{fileName}"
}, installerAddress, new ModBase.FileChecker(64 * 1024, hash: forge.Hash)));
}
task.output = files;
})
{
ProgressWeight = 0.5d,
show = false
});
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLoaderMainFile", loaderName),
new List<DownloadFile>())
{ ProgressWeight = 9d });
// 安装(仅在新版安装时需要原版 Jar)
if (forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge || Convert.ToDouble(loaderVersion.BeforeFirst(".")) >= 20d)
{
ModBase.Log($"[Download] 检测为{(forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Forge ? "新版 Forge" : " " + forgeType)}" + loaderVersion);
List<ModLibrary.McLibToken> libs = null;
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeLoaderLibraries", loaderName), task =>
{
task.output = new List<DownloadFile>();
ZipArchive installer = null;
try
{
// 解压并获取、合并两个 Json 的信息
ModBase.WaitForFileReady(installerAddress);
installer = new ZipArchive(new FileStream(installerAddress, FileMode.Open));
task.Progress = 0.2d;
var json = (JsonObject)ModBase.GetJson(
ModBase.ReadFile(installer.GetEntry("install_profile.json").Open()));
var json2 = (JsonObject)ModBase.GetJson(ModBase.ReadFile(installer.GetEntry("version.json").Open()));
json.Merge(json2);
// 如果是 1.16.5 就升级一下 Authlib
if (inherit == "1.16.5" && (bool)Config.Download.FixAuthLib)
json = (JsonObject)ModBase.GetJson(json.ToString()
.Replace("2.1.28/authlib-2.1.28.jar", "2.3.31/authlib-2.3.31.jar")
.Replace("com.mojang:authlib:2.1.28", "com.mojang:authlib:2.3.31")
.Replace("ad54da276bf59983d02d5ed16fc14541354c71fd",
"bbd00ca33b052f73a6312254780fc580d2da3535").Replace("76328", "87662"));
// 获取 Lib 下载信息
libs = ModLibrary.McLibListGetWithJson(json, true);
// 添加 Mappings 下载信息
if (json["data"] is not null && json["data"]["MOJMAPS"] is not null)
{
// 下载原版 Json 文件
task.Progress = 0.4d;
var rawJson = (JsonObject)ModBase.GetJson(ModNet.NetGetCodeByLoader(
ModDownload.DlSourceLauncherOrMetaGet(
ModDownload.DlClientListGet(inherit)?.ToString()), isJson: true));
// [net.minecraft:client:1.17.1-20210706.113038:mappings@txt] 或 @tsrg]
var originalName = json["data"]["MOJMAPS"]["client"].ToString().Trim("[]".ToCharArray())
.BeforeFirst("@");
var address = ModLibrary.McLibGet(originalName).Replace(".jar",
"-mappings." + json["data"]["MOJMAPS"]["client"].ToString().Trim("[]".ToCharArray())
.Split("@")[1]);
var clientMappings = rawJson["downloads"]["client_mappings"];
libs.Add(new ModLibrary.McLibToken
{
IsNatives = false,
LocalPath = address,
OriginalName = originalName,
Url = (string)clientMappings["url"],
size = (long)clientMappings["size"],
Sha1 = (string)clientMappings["sha1"]
});
ModBase.Log(
$"[Download] 需要下载 Mappings{clientMappings["url"]} (SHA1: {clientMappings["sha1"]})");
}
task.Progress = 0.8d;
// 去除其中的原始 Forgelike 项
for (int i = 0, loopTo = libs.Count - 1; i <= loopTo; i++)
if (libs[i].LocalPath.EndsWithF($"{loaderName.ToLower()}-{inherit}-{loaderVersion}.jar") ||
libs[i].LocalPath.EndsWithF($"{loaderName.ToLower()}-{inherit}-{loaderVersion}-client.jar"))
{
ModBase.Log($"[Download] 已从待下载 {loaderName} 支持库中移除:" + libs[i].LocalPath,
ModBase.LogLevel.Debug);
libs.RemoveAt(i);
break;
}
task.output = ModLibrary.McLibNetFilesFromTokens(libs);
}
catch (Exception ex)
{
throw new Exception(
Lang.Text("Minecraft.Download.Error.LoaderLibraryListFailed",
forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Forge
? Lang.Text("Minecraft.Download.Loader.NewForge")
: " " + forgeType), ex);
}
finally
{
// 释放文件
if (installer is not null)
installer.Dispose();
}
})
{
ProgressWeight = 2d
});
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLoaderLibraries", loaderName),
new List<DownloadFile>())
{ ProgressWeight = 12d });
loaders.Add(new ModLoader.LoaderTask<List<DownloadFile>, bool>(
Lang.Text("Minecraft.Download.Stage.GetLoaderLibraries", loaderName), task =>
{
#region Forgelike 文件
if (isCustomFolder)
foreach (var LibFile in libs)
{
var realPath = LibFile.LocalPath.Replace(ModFolder.mcFolderSelected, mcFolder);
if (!File.Exists(realPath))
{
Directory.CreateDirectory(Path.GetDirectoryName(realPath));
ModBase.CopyFile(LibFile.LocalPath, realPath);
}
if (ModBase.modeDebug)
ModBase.Log($"[Download] 复制的 {loaderName} 支持库文件:" + LibFile.LocalPath);
}
#endregion
#region 原版文件
// 等待原版文件下载完成
if (clientDownloadLoader is null)
return;
var targetLoaders = clientDownloadLoader.GetLoaderList()
.Where(l => (l.name ?? "") == mcDownloadClientLibName || (l.name ?? "") == mcDownloadClientJsonName)
.Where(l => l.State != ModBase.LoadState.Finished).ToList();
if (targetLoaders.Any())
ModBase.Log($"[Download] {loaderName} 安装正在等待原版文件下载完成");
while (targetLoaders.Any() && !task.IsAborted)
{
targetLoaders = targetLoaders.Where(l => l.State != ModBase.LoadState.Finished).ToList();
Thread.Sleep(50);
}
if (task.IsAborted)
return;
// 拷贝原版文件
if (!isCustomFolder)
return;
lock (vanillaSyncLock)
{
var clientName = ModBase.GetFolderNameFromPath(clientFolder);
Directory.CreateDirectory(Path.Combine(mcFolder, "versions", inherit));
if (!File.Exists(Path.Combine(mcFolder, "versions", inherit, inherit + ".json")))
ModBase.CopyFile(Path.Combine(clientFolder, clientName + ".json"),
Path.Combine(mcFolder, "versions", inherit, inherit + ".json"));
if (!File.Exists(Path.Combine(mcFolder, "versions", inherit, inherit + ".jar")))
ModBase.CopyFile(Path.Combine(clientFolder, clientName + ".jar"),
Path.Combine(mcFolder, "versions", inherit, inherit + ".jar"));
}
#endregion
})
{
ProgressWeight = 0.1d,
show = false
});
loaders.Add(new ModLoader.LoaderTask<bool, bool>(
forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Forge
? Lang.Text("Minecraft.Download.Stage.InstallForge.MethodA")
: Lang.Text("Minecraft.Download.Stage.InstallForgeType", forgeType), task =>
{
ModBase.WaitForFileReady(installerAddress);
var installer = new ZipArchive(new FileStream(installerAddress, FileMode.Open));
try
{
// 记录当前文件夹列表(在新建目标文件夹之前)
ModBase.Log("[Download] 开始进行 Forgelike 安装:" + installerAddress);
// 解压并获取信息
var oldList = new DirectoryInfo(mcFolder + "versions/")
.EnumerateDirectories().Select(i => i.FullName).ToList();
// 新建目标实例文件夹
var json = ModBase.GetJson(ModBase.ReadFile(installer.GetEntry("install_profile.json").Open()));
Directory.CreateDirectory(versionFolder);
task.Progress = 0.04d;
// 释放 launcher_installer.json
ModFolder.McFolderLauncherProfilesJsonCreate(mcFolder);
task.Progress = 0.05d;
// 运行 Forge 安装器
var useJavaWrapper = ModBase.IsUtf8CodePage();
Retry:
try
{
// 释放 Forge 注入器
ModBase.WriteFile(Path.Combine(ModBase.pathTemp, "Cache", "forge_installer.jar"),
ModBase.GetResourceStream("Resources/forge-installer.jar"));
task.Progress = 0.06d;
// 运行注入器
ForgelikeInjector(installerAddress, task, mcFolder, useJavaWrapper, forgeType);
task.Progress = 0.97d;
}
catch (Exception ex)
{
if (!useJavaWrapper)
{
ModBase.Log(ex, $"不使用 JavaWrapper 安装 {loaderName} 失败,将使用 JavaWrapper 并重试");
useJavaWrapper = true;
goto Retry;
}
throw new Exception(
Lang.Text("Minecraft.Download.Error.LoaderInstallerRunFailed", loaderName), ex);
// 拷贝新增的实例 Json
}
var deltaList = new DirectoryInfo(mcFolder + "versions/").EnumerateDirectories()
.SkipWhile(i => oldList.Contains(i.FullName)).ToList();
if (deltaList.Count > 1)
// 它可能和 OptiFine 安装同时运行,导致增加的文件不止一个(这导致了 #151)
// 也可能是因为 Forge 安装器的 Bug,生成了一个名字错误的文件夹,所以需要检查文件夹是否为空
deltaList = deltaList
.Where(l => l.Name.ContainsF("forge", true) && l.EnumerateFiles().Any())
.ToList();
// 如果没有新增文件夹,那么预测的文件夹名就是正确的
// 如果只新增 1 个文件夹,那么拷贝 Json 文件
if (deltaList.Count == 1)
{
var jsonFile = deltaList[0].EnumerateFiles().First();
ModBase.WriteFile(Path.Combine(versionFolder, targetVersion + ".json"),
ModBase.ReadFile(jsonFile.FullName));
ModBase.Log(
$"[Download] 已拷贝新增的实例 Json 文件:{jsonFile.FullName} -> {versionFolder}{targetVersion}.json");
}
else if (deltaList.Count > 1)
{
// 新增了多个文件夹
//Enumerable.Select<string>((IEnumerable<DirectoryInfo>)DeltaList, d => d.Name).Join(";")
ModBase.Log(
$"[Download] 有多个疑似的新增实例,无法确定:{string.Join(";", deltaList.Select<DirectoryInfo, string>(d => d.Name))}");
}
else
{
// 没有新增文件夹
ModBase.Log("[Download] 未找到新增的实例文件夹");
}
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.LoaderInstallFailed", loaderName), ex);
}
finally
{
// 清理文件
try
{
if (installer is not null)
installer.Dispose();
if (File.Exists(installerAddress))
File.Delete(installerAddress);
}
catch (Exception ex)
{
ModBase.Log(ex, $"安装 {loaderName} 清理文件时出错");
}
}
})
{
ProgressWeight = 10d
});
}
else
{
ModBase.Log("[Download] 检测为非新版 Forge" + loaderVersion);
loaders.Add(new ModLoader.LoaderTask<List<DownloadFile>, bool>(
$"{(forgeType == ModDownload.DlForgelikeEntry.ForgelikeType.Forge ? Lang.Text("Minecraft.Download.Stage.InstallForge.MethodB") : Lang.Text("Minecraft.Download.Stage.InstallForgeType", forgeType))}",
task =>
{
ZipArchive installer = null;
try
{
// 解压并获取信息
ModBase.WaitForFileReady(installerAddress);
installer = new ZipArchive(new FileStream(installerAddress, FileMode.Open));
task.Progress = 0.2d;
var json = (JsonObject)ModBase.GetJson(
ModBase.ReadFile(installer.GetEntry("install_profile.json").Open()));
task.Progress = 0.4d;
// 新建实例文件夹
Directory.CreateDirectory(versionFolder);
task.Progress = 0.5d;
if (json["install"] is null)
{
// 中版:Legacy 方式 1
ModBase.Log("[Download] 开始进行 Forge 安装,Legacy 方式 1" + installerAddress);
// 建立 Json 文件
var jsonVersion = (JsonObject)ModBase.GetJson(
ModBase.ReadFile(installer.GetEntry(json["json"].ToString().TrimStart('/')).Open()));
jsonVersion["id"] = targetVersion;
ModBase.WriteFile(Path.Combine(versionFolder, targetVersion + ".json"), jsonVersion.ToString());
task.Progress = 0.6d;
// 解压支持库文件
installer.Dispose();
var unrarDir = Path.Combine(Path.GetDirectoryName(installerAddress), "_unrar");
ModBase.ExtractFile(installerAddress, unrarDir);
ModBase.CopyDirectory(Path.Combine(unrarDir, "maven"), Path.Combine(mcFolder, "libraries"));
ModBase.DeleteDirectory(unrarDir);
}
else
{
// 旧版:Legacy 方式 2
ModBase.Log("[Download] 开始进行 Forge 安装,Legacy 方式 2" + installerAddress);
// 解压 Jar 文件
var jarAddress = ModLibrary.McLibGet((string)json["install"]["path"],
customMcFolder: mcFolder);
if (File.Exists(jarAddress))
File.Delete(jarAddress);
ModBase.WriteFile(jarAddress,
installer.GetEntry((string)json["install"]["filePath"]).Open());
task.Progress = 0.9d;
// 建立 Json 文件
json["versionInfo"]["id"] = targetVersion;
if (json["versionInfo"]["inheritsFrom"] is null)
((JsonObject)json["versionInfo"]).Add("inheritsFrom", inherit);
ModBase.WriteFile(Path.Combine(versionFolder, targetVersion + ".json"), json["versionInfo"].ToString());
}
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.ForgeOldInstallFailed"), ex);
}
finally
{
try
{
// 清理文件
if (installer is not null)
installer.Dispose();
if (File.Exists(installerAddress))
File.Delete(installerAddress);
var unrarDir = Path.Combine(Path.GetDirectoryName(installerAddress), "_unrar");
if (Directory.Exists(unrarDir))
ModBase.DeleteDirectory(unrarDir);
}
catch (Exception ex)
{
ModBase.Log(ex, "非新版方式安装 Forge 清理文件时出错");
}
}
})
{
ProgressWeight = 1d
});
}
return loaders;
}
#endregion
#region Forge 下载菜单
public static void ForgeDownloadListItemPreload(StackPanel stack, List<ModDownload.DlForgeVersionEntry> entries,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
// 如果只有一个版本,则不特别列出
if (entries.Count == 1)
return;
// 获取推荐版本与最新版本
ModDownload.DlForgeVersionEntry freshVersion = null;
if (entries.Any())
freshVersion = entries[0];
else
ModBase.Log("[System] 未找到可用的 Forge 版本", ModBase.LogLevel.Debug);
ModDownload.DlForgeVersionEntry recommendedVersion = null;
foreach (var Entry in entries)
if (Entry.IsRecommended)
recommendedVersion = Entry;
// 若推荐版本与最新版本为同一版本,则仅显示推荐版本
if (freshVersion is not null && ReferenceEquals(freshVersion, recommendedVersion))
freshVersion = null;
// 显示各个版本
if (recommendedVersion is not null)
{
var recommended = ForgeDownloadListItem(recommendedVersion, onClick, isSaveOnly);
recommended.Info = Lang.Text("Download.Version.Type.Recommended") + (string.IsNullOrEmpty(recommended.Info) ? "" : " | " + recommended.Info);
stack.Children.Add(recommended);
}
if (freshVersion is not null)
{
var fresh = ForgeDownloadListItem(freshVersion, onClick, isSaveOnly);
fresh.Info = Lang.Text("Download.Version.Latest.Title") + (string.IsNullOrEmpty(fresh.Info) ? "" : " | " + fresh.Info);
stack.Children.Add(fresh);
}
// 添加间隔
stack.Children.Add(new TextBlock
{
Text = Lang.Text("Download.Version.AllVersions", entries.Count), HorizontalAlignment = HorizontalAlignment.Left,
Margin = new Thickness(6d, 13d, 0d, 4d)
});
}
public static MyListItem ForgeDownloadListItem(ModDownload.DlForgeVersionEntry entry,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
var infoParts = new List<string>();
if (!string.IsNullOrEmpty(entry.ReleaseTime))
infoParts.Add(Lang.Text("Download.Version.ReleaseDate", entry.ReleaseTime));
if (ModBase.modeDebug)
infoParts.Add(Lang.Text("Download.Version.Forge.Type", entry.Category));
// 建立控件
var newItem = new MyListItem
{
Title = entry.VersionName,
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = string.Join(" | ", infoParts),
Logo = ModBase.pathImage + "Blocks/Anvil.png"
};
newItem.Click += onClick;
// 建立菜单
if (isSaveOnly)
newItem.ContentHandler = ForgeSaveContMenuBuild;
else
newItem.ContentHandler = ForgeContMenuBuild;
// 结束
return newItem;
}
private static void ForgeContMenuBuild(MyListItem sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
btnSave.Click += (ss, ee) => ForgeSave_Click(ss, (dynamic)ee);
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (ss, ee) => ForgeLog_Click(ss, (dynamic)ee);
sender.Buttons = new[] { btnSave, btnInfo };
}
private static void ForgeSaveContMenuBuild(MyListItem sender, EventArgs e)
{
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (ss, ee) => ForgeLog_Click(ss, (dynamic)e);
sender.Buttons = new[] { btnInfo };
}
private static void ForgeLog_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlForgeVersionEntry version;
if (((dynamic)sender).Tag is not null)
version = (ModDownload.DlForgeVersionEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
version = (ModDownload.DlForgeVersionEntry)((dynamic)sender).Parent.Tag;
else
version = (ModDownload.DlForgeVersionEntry)((dynamic)sender).Parent.Parent.Tag;
ModBase.OpenWebsite(
$"https://files.minecraftforge.net/maven/net/minecraftforge/forge/{version.Inherit}-{version.VersionName}/forge-{version.Inherit}-{version.VersionName}-changelog.txt");
}
public static void ForgeSave_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlForgeVersionEntry version;
if (((dynamic)sender).Tag is not null)
version = (ModDownload.DlForgeVersionEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
version = (ModDownload.DlForgeVersionEntry)((dynamic)sender).Parent.Tag;
else
version = (ModDownload.DlForgeVersionEntry)((dynamic)sender).Parent.Parent.Tag;
McDownloadForgelikeSave(version);
}
#endregion
#region Forge 推荐版本获取
/// <summary>
/// 尝试刷新 Forge 推荐版本缓存。
/// </summary>
public static void McDownloadForgeRecommendedRefresh()
{
if (isForgeRecommendedRefreshed)
return;
isForgeRecommendedRefreshed = true;
// 获取所有推荐版本列表
// 内容为:"1.15.2":"31.2.0"
// 保存
ModBase.RunInNewThread(() =>
{
try
{
ModBase.Log("[Download] 刷新 Forge 推荐版本缓存开始");
var result = ModNet.NetGetCodeByLoader("https://bmclapi2.bangbang93.com/forge/promos");
if (result.Length < 1000) throw new Exception(Lang.Text("Minecraft.Download.Error.ForgePromosResultTooShort", result));
var resultJson = (JsonNode)ModBase.GetJson(result);
var recommendedList = new List<string>();
foreach (JsonObject Version in resultJson.AsArray())
{
if (Version["name"] is null || Version["build"] is null) continue;
var name = (string)Version["name"];
if (!name.EndsWithF("-recommended")) continue;
recommendedList.Add("\"" + name.Replace("-recommended",
"\":\"" + Version["build"]["version"] + "\""));
}
if (recommendedList.Count < 5)
throw new Exception(Lang.Text("Minecraft.Download.Error.ForgeRecommendedTooFew", result));
var cacheJson = "{" + recommendedList.Join(",") + "}";
ModBase.WriteFile(Path.Combine(ModBase.pathTemp, "Cache", "ForgeRecommendedList.json"), cacheJson);
ModBase.Log("[Download] 刷新 Forge 推荐版本缓存成功");
}
catch (Exception ex)
{
ModBase.Log(ex, "刷新 Forge 推荐版本缓存失败");
}
}, "ForgeRecommendedRefresh");
}
private static bool isForgeRecommendedRefreshed;
/// <summary>
/// 尝试获取某个 MC 版本对应的 Forge 推荐版本。如果不可用会返回 Nothing。
/// </summary>
public static string McDownloadForgeRecommendedGet(string mcInstance)
{
try
{
if (mcInstance is null)
return null;
var list = ModBase.ReadFile(Path.Combine(ModBase.pathTemp, "Cache", "ForgeRecommendedList.json"));
if (list is null || string.IsNullOrEmpty(list))
{
ModBase.Log("[Download] 没有 Forge 推荐版本缓存文件");
return null;
}
var json = (JsonObject)ModBase.GetJson(list);
if (json is null || !(mcInstance ?? "null").Contains(".") || !json.ContainsKey(mcInstance))
return null;
return (json[mcInstance] ?? "").ToString();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"获取 Forge 推荐版本失败(" + (mcInstance ?? "null") + "",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
return null;
}
}
#endregion
#region NeoForge 下载菜单
public static void NeoForgeDownloadListItemPreload(StackPanel stack, List<ModDownload.DlNeoForgeListEntry> entries,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
// 如果只有一个版本,则不特别列出
if (entries.Count == 1)
return;
// 获取最新稳定版和测试版
ModDownload.DlNeoForgeListEntry freshStableVersion = null;
ModDownload.DlNeoForgeListEntry freshBetaVersion = null;
if (entries.Any())
foreach (var Entry in entries.ToList())
if (Entry.IsBeta)
{
if (freshBetaVersion is null)
freshBetaVersion = Entry;
}
else
{
freshStableVersion = Entry;
break;
}
else
ModBase.Log("[System] 未找到可用的 NeoForge 版本", ModBase.LogLevel.Debug);
// 显示各个版本
if (freshStableVersion is not null)
{
var fresh = NeoForgeDownloadListItem(freshStableVersion, onClick, isSaveOnly);
fresh.Info = string.IsNullOrEmpty(fresh.Info)
? Lang.Text("Download.Version.Fresh.Stable")
: Lang.Text("Download.Version.Fresh.Latest") + " | " + fresh.Info;
stack.Children.Add(fresh);
}
if (freshBetaVersion is not null)
{
var fresh = NeoForgeDownloadListItem(freshBetaVersion, onClick, isSaveOnly);
fresh.Info = string.IsNullOrEmpty(fresh.Info)
? Lang.Text("Download.Version.Fresh.Development")
: Lang.Text("Download.Version.Fresh.Latest") + " | " + fresh.Info;
stack.Children.Add(fresh);
}
// 添加间隔
stack.Children.Add(new TextBlock
{
Text = Lang.Text("Download.Version.AllVersions", entries.Count), HorizontalAlignment = HorizontalAlignment.Left,
Margin = new Thickness(6d, 13d, 0d, 4d)
});
}
public static MyListItem NeoForgeDownloadListItem(ModDownload.DlNeoForgeListEntry info,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
// 建立控件
var newItem = new MyListItem
{
Title = info.VersionName,
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = info,
Info = info.IsBeta ? Lang.Text("Download.Version.Type.Preview") : Lang.Text("Download.Version.Type.Stable"),
Logo = ModBase.pathImage + "Blocks/NeoForge.png"
};
newItem.Click += onClick;
// 建立菜单
if (isSaveOnly)
newItem.ContentHandler = NeoForgeSaveContMenuBuild;
else
newItem.ContentHandler = NeoForgeContMenuBuild;
// 结束
return newItem;
}
private static void NeoForgeContMenuBuild(MyListItem sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
btnSave.Click += (sender, e) => NeoForgeSave_Click(sender, (RoutedEventArgs)e);
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (sender, e) => NeoForgeLog_Click(sender, (RoutedEventArgs)e);
sender.Buttons = new[] { btnSave, btnInfo };
}
private static void NeoForgeSaveContMenuBuild(MyListItem sender, EventArgs e)
{
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (sender, e) => NeoForgeLog_Click(sender, (RoutedEventArgs)e);
sender.Buttons = new[] { btnInfo };
}
private static void NeoForgeLog_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlNeoForgeListEntry info;
if (((dynamic)sender).Tag is not null)
info = (ModDownload.DlNeoForgeListEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
info = (ModDownload.DlNeoForgeListEntry)((dynamic)sender).Parent.Tag;
else
info = (ModDownload.DlNeoForgeListEntry)((dynamic)sender).Parent.Parent.Tag;
ModBase.OpenWebsite(info.UrlBase + "-changelog.txt");
}
public static void NeoForgeSave_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlNeoForgeListEntry info;
if (((dynamic)sender).Tag is not null)
info = (ModDownload.DlNeoForgeListEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
info = (ModDownload.DlNeoForgeListEntry)((dynamic)sender).Parent.Tag;
else
info = (ModDownload.DlNeoForgeListEntry)((dynamic)sender).Parent.Parent.Tag;
McDownloadForgelikeSave(info);
}
#endregion
#region Cleanroom 下载菜单
public static void CleanroomDownloadListItemPreload(StackPanel stack,
List<ModDownload.DlCleanroomListEntry> entries, MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
// 获取最新稳定版和测试版
// Dim FreshStableVersion As DlCleanroomListEntry = Nothing
ModDownload.DlCleanroomListEntry freshBetaVersion = null;
if (entries.Any())
freshBetaVersion = entries[0];
else
ModBase.Log("[System] 未找到可用的 Cleanroom 版本", ModBase.LogLevel.Debug);
if (freshBetaVersion is not null)
{
var fresh = CleanroomDownloadListItem(freshBetaVersion, onClick, isSaveOnly);
fresh.Info = string.IsNullOrEmpty(fresh.Info) ? Lang.Text("Download.Version.Fresh.Development") : Lang.Text("Download.Version.Fresh.Latest") + " | " + fresh.Info;
stack.Children.Add(fresh);
}
// 添加间隔
stack.Children.Add(new TextBlock
{
Text = Lang.Text("Download.Version.AllVersions", entries.Count), HorizontalAlignment = HorizontalAlignment.Left,
Margin = new Thickness(6d, 13d, 0d, 4d)
});
}
public static MyListItem CleanroomDownloadListItem(ModDownload.DlCleanroomListEntry info,
MyListItem.ClickEventHandler onClick, bool isSaveOnly)
{
// 建立控件
var newItem = new MyListItem
{
Title = info.VersionName,
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = info,
Info = info.IsBeta ? Lang.Text("Download.Version.Type.Preview") : Lang.Text("Download.Version.Type.Stable"),
Logo = ModBase.pathImage + "Blocks/Cleanroom.png"
};
newItem.Click += onClick;
// 建立菜单
if (isSaveOnly)
newItem.ContentHandler = CleanroomSaveContMenuBuild;
else
newItem.ContentHandler = CleanroomContMenuBuild;
// 结束
return newItem;
}
private static void CleanroomContMenuBuild(MyListItem sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
btnSave.Click += (sender, _e) => CleanroomSave_Click(sender, (RoutedEventArgs)e);
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (sender, e) => CleanroomLog_Click(sender, (RoutedEventArgs)e);
sender.Buttons = new[] { btnSave, btnInfo };
}
private static void CleanroomSaveContMenuBuild(MyListItem sender, EventArgs e)
{
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (a, b) => CleanroomLog_Click(a, (dynamic)b);
sender.Buttons = new[] { btnInfo };
}
private static void CleanroomLog_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlCleanroomListEntry info;
if (((dynamic)sender).Tag is not null)
info = (ModDownload.DlCleanroomListEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
info = (ModDownload.DlCleanroomListEntry)((dynamic)sender).Parent.Tag;
else
info = (ModDownload.DlCleanroomListEntry)((dynamic)sender).Parent.Parent.Tag;
ModBase.OpenWebsite(info.UrlBase + "-changelog.txt");
}
public static void CleanroomSave_Click(object sender, RoutedEventArgs e)
{
ModDownload.DlCleanroomListEntry info;
if (((dynamic)sender).Tag is not null)
info = (ModDownload.DlCleanroomListEntry)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
info = (ModDownload.DlCleanroomListEntry)((dynamic)sender).Parent.Tag;
else
info = (ModDownload.DlCleanroomListEntry)((dynamic)sender).Parent.Parent.Tag;
McDownloadForgelikeSave(info);
}
#endregion
#region Fabric 下载
public static void McDownloadFabricLoaderSave(JsonObject downloadInfo)
{
try
{
var url = downloadInfo["url"].ToString();
var fileName = ModBase.GetFileNameFromPath(url);
var version = ModBase.GetFileNameFromPath(downloadInfo["version"].ToString());
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"), fileName, Lang.Text("Download.Version.Installer.Fabric.Filter"));
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.FabricInstallerDownload", version) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
// 下载
// BMCLAPI 不支持 Fabric Installer 下载
var address = new List<string>();
address.Add(url);
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadMainFile"),
new List<DownloadFile> { new(address.ToArray(), target, new ModBase.FileChecker(1024 * 64)) })
{ ProgressWeight = 15d });
// 启动
var loader =
new ModLoader.LoaderCombo<JsonObject>(
Lang.Text("Minecraft.Download.Stage.FabricInstallerDownload", version), loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(downloadInfo);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 Fabric 安装器下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
/// <summary>
/// 获取下载某个 Fabric 实例的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadFabricLoader(string fabricVersion, string minecraftName,
string mcFolder = null, bool fixLibrary = true)
{
// 参数初始化
mcFolder = mcFolder ?? ModFolder.mcFolderSelected;
var isCustomFolder = (mcFolder ?? "") != (ModFolder.mcFolderSelected ?? "");
var id = "fabric-loader-" + fabricVersion + "-" + minecraftName;
var versionFolder = Path.Combine(mcFolder, "versions", id);
var loaders = new List<ModLoader.LoaderBase>();
// 下载 Json
minecraftName = minecraftName.Replace("∞", "infinite"); // 放在 ID 后面避免影响实例文件夹名称
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.ObtainFabricMainFileUrl"), task =>
{
// 启动依赖实例的下载
if (fixLibrary)
McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, minecraftName);
task.Progress = 0.5d;
var safeName = minecraftName.Replace("∞", "infinite");
var bmclapiUrl = $"https://bmclapi2.bangbang93.com/fabric-meta/v2/versions/loader/{safeName}/{fabricVersion}/profile/json";
var officialUrl = $"https://meta.fabricmc.net/v2/versions/loader/{safeName}/{fabricVersion}/profile/json";
string json = null;
foreach (var url in new[] { bmclapiUrl, officialUrl })
{
try
{
json = Requester.FetchString(url, new RequestParam { UseBrowserUserAgent = true, Timeout = 5000, Retries = 2 });
if (json is not null) break;
}
catch (Exception ex)
{
ModBase.Log(ex, $"[Download] 从 {url} 下载 Fabric meta 失败");
}
}
if (json is null)
throw new Exception(Lang.Text("Minecraft.Download.Error.FabricMetaDownloadFailed",
$"{bmclapiUrl} and {officialUrl}"));
Directory.CreateDirectory(versionFolder);
File.WriteAllText(Path.Combine(versionFolder, id + ".json"), json, Encoding.UTF8);
task.output = new List<DownloadFile>();
})
{
ProgressWeight = 0.5d
});
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLoaderMainFile", "Fabric"),
new List<DownloadFile>()) { ProgressWeight = 2.5d });
// 下载支持库
if (fixLibrary)
{
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeFabricLibraries"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder)))
{ ProgressWeight = 1d, show = false });
loaders.Add(
new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLabyModClientJson"),
new List<DownloadFile>()) { ProgressWeight = 8d });
}
return loaders;
}
#endregion
#region LegacyFabric 下载
public static void McDownloadLegacyFabricLoaderSave(JsonObject downloadInfo)
{
try
{
var url = downloadInfo["url"].ToString();
var fileName = ModBase.GetFileNameFromPath(url);
var version = ModBase.GetFileNameFromPath(downloadInfo["version"].ToString());
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"), fileName, Lang.Text("Download.Version.Installer.LegacyFabric.Filter"));
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.LegacyFabricInstallerDownload", version) ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
// 下载
var address = new List<string>();
address.Add(url);
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadMainFile"),
new List<DownloadFile> { new(address.ToArray(), target, new ModBase.FileChecker(1024 * 64)) })
{ ProgressWeight = 15d });
// 启动
var loader =
new ModLoader.LoaderCombo<JsonObject>(
Lang.Text("Minecraft.Download.Stage.LegacyFabricInstallerDownload", version), loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start(downloadInfo);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 Legacy Fabric 安装器下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
/// <summary>
/// 获取下载某个 LegacyFabric 实例的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadLegacyFabricLoader(string legacyFabricVersion,
string minecraftName, string mcFolder = null, bool fixLibrary = true)
{
// 参数初始化
mcFolder = mcFolder ?? ModFolder.mcFolderSelected;
var isCustomFolder = (mcFolder ?? "") != (ModFolder.mcFolderSelected ?? "");
var id = "legacy-fabric-loader-" + legacyFabricVersion + "-" + minecraftName;
var versionFolder = Path.Combine(mcFolder, "versions", id);
var loaders = new List<ModLoader.LoaderBase>();
// 下载 Json
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.ObtainLegacyFabricMainFileUrl"), task =>
{
// 启动依赖实例的下载
if (fixLibrary)
McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, minecraftName);
task.Progress = 0.5d;
// 构造文件请求
task.output = new List<DownloadFile>
{
new(
new[]
{
"https://meta.legacyfabric.net/v2/versions/loader/" + minecraftName + "/" +
legacyFabricVersion + "/profile/json"
}, Path.Combine(versionFolder, id + ".json"), new ModBase.FileChecker(isJson: true))
};
})
{
ProgressWeight = 0.5d
});
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLoaderMainFile", "Legacy Fabric"),
new List<DownloadFile>())
{ ProgressWeight = 2.5d });
// 下载支持库
if (fixLibrary)
{
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeLegacyFabricLibraries"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder)))
{ ProgressWeight = 1d, show = false });
loaders.Add(new LoaderDownload(
Lang.Text("Minecraft.Download.Stage.DownloadLoaderLibraries", "Legacy Fabric"),
new List<DownloadFile>())
{ ProgressWeight = 8d });
}
return loaders;
}
#endregion
#region Fabric 下载菜单
public static MyListItem FabricDownloadListItem(JsonObject entry, MyListItem.ClickEventHandler onClick)
{
// 建立控件
var newItem = new MyListItem
{
Title = entry["version"].ToString().Replace("+build", ""),
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = entry["stable"].ToObject<bool>() ? Lang.Text("Download.Version.Type.Stable") : Lang.Text("Download.Version.Type.Preview"),
Logo = ModBase.pathImage + "Blocks/Fabric.png"
};
newItem.Click += onClick;
newItem.ContentHandler = FabricContMenuBuild;
// 结束
return newItem;
}
private static void FabricContMenuBuild(object sender, EventArgs e)
{
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (a, b) => FabricLog_Click(a, (dynamic)b);
((dynamic)sender).Buttons = new[] { btnInfo };
}
private static void FabricLog_Click(object sender, RoutedEventArgs e)
{
ModBase.OpenWebsite("https://fabricmc.net/blog");
}
public static MyListItem FabricApiDownloadListItem(ModComp.CompFile entry, MyListItem.ClickEventHandler onClick)
{
// 建立控件
var newItem = new MyListItem
{
Title = entry.DisplayName.Split("]")[1].Replace("Fabric API ", "").Replace(" build ", ".").Trim(),
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = entry.StatusDescription + Lang.Text("Download.Version.ReleaseDate", Lang.Date(entry.ReleaseDate, "g")),
Logo = ModBase.pathImage + "Blocks/Fabric.png"
};
newItem.Click += onClick;
// 结束
return newItem;
}
public static MyListItem OptiFabricDownloadListItem(ModComp.CompFile entry, MyListItem.ClickEventHandler onClick)
{
// 建立控件
var newItem = new MyListItem
{
Title = entry.DisplayName.ToLower().Replace("optifabric-", "").Replace(".jar", "").Trim().TrimStart('v'),
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = entry.StatusDescription + Lang.Text("Download.Version.ReleaseDate", Lang.Date(entry.ReleaseDate, "g")),
Logo = ModBase.pathImage + "Blocks/OptiFabric.png"
};
newItem.Click += onClick;
// 结束
return newItem;
}
#endregion
#region LegacyFabric 下载菜单
public static MyListItem LegacyFabricDownloadListItem(JsonObject entry, MyListItem.ClickEventHandler onClick)
{
// 建立控件
var newItem = new MyListItem
{
Title = entry["version"].ToString(),
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = entry["stable"].ToObject<bool>() ? Lang.Text("Download.Version.Type.Stable") : Lang.Text("Download.Version.Type.Preview"),
Logo = ModBase.pathImage + "Blocks/Fabric.png"
};
newItem.Click += onClick;
// 结束
return newItem;
}
public static MyListItem LegacyFabricApiDownloadListItem(ModComp.CompFile entry,
MyListItem.ClickEventHandler onClick)
{
// 建立控件
var newItem = new MyListItem
{
Title = entry.DisplayName.Replace("Legacy Fabric API ", ""),
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = entry.StatusDescription + Lang.Text("Download.Version.ReleaseDate", Lang.Date(entry.ReleaseDate, "g")),
Logo = ModBase.pathImage + "Blocks/Fabric.png"
};
newItem.Click += onClick;
// 结束
return newItem;
}
#endregion
#region LabyMod 下载
public static void McDownloadLabyModProductionLoaderSave()
{
try
{
var url = "https://releases.labymod.net/api/v1/installer/production/java";
var fileName = "LabyMod4ProductionInstaller.jar";
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"), fileName, Lang.Text("Download.Version.Installer.LabyMod.Filter"));
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.LabyModInstallerDownload") ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
// 下载
var address = new List<string>();
address.Add(url);
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadMainFile"),
new List<DownloadFile> { new(address.ToArray(), target, new ModBase.FileChecker(1024 * 64)) })
{ ProgressWeight = 15d });
// 启动
var loader =
new ModLoader.LoaderCombo<JsonObject>(Lang.Text("Minecraft.Download.Stage.LabyModInstallerDownload"),
loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start();
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 LabyMod 安装器下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
public static void McDownloadLabyModSnapshotLoaderSave()
{
try
{
var url = "https://releases.labymod.net/api/v1/installer/snapshot/java";
var fileName = "LabyMod4SnapshotInstaller.jar";
var target = SystemDialogs.SelectSaveFile(Lang.Text("Download.Version.SelectSaveLocation"), fileName, Lang.Text("Download.Version.Installer.LabyMod.Filter"));
if (!target.Contains(@"\"))
return;
// 重复任务检查
foreach (var OngoingLoader in ModLoader.loaderTaskbar.ToList())
{
if ((OngoingLoader.name ?? "") !=
(Lang.Text("Minecraft.Download.Stage.LabyModInstallerDownload") ?? ""))
continue;
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceDownloading"), HintType.Error);
return;
}
// 构造步骤加载器
var loaders = new List<ModLoader.LoaderBase>();
// 下载
var address = new List<string>();
address.Add(url);
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadMainFile"),
new List<DownloadFile> { new(address.ToArray(), target, new ModBase.FileChecker(1024 * 64)) })
{ ProgressWeight = 15d });
// 启动
var loader =
new ModLoader.LoaderCombo<JsonObject>(Lang.Text("Minecraft.Download.Stage.LabyModInstallerDownload"),
loaders)
{ OnStateChanged = LoaderStateChangedHintOnly };
loader.Start();
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始 LabyMod 安装器下载失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
}
}
/// <summary>
/// 获取下载某个 LabyMod 实例的加载器列表。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadLabyModLoader(string labyModCommitRef, string labyModChannel,
string minecraftName, string mcFolder = null, bool fixLibrary = true)
{
// 参数初始化
mcFolder = mcFolder ?? ModFolder.mcFolderSelected;
var isCustomFolder = (mcFolder ?? "") != (ModFolder.mcFolderSelected ?? "");
var id = "labymod-" + labyModCommitRef + "-" + minecraftName;
var versionFolder = Path.Combine(mcFolder, "versions", id);
var loaders = new List<ModLoader.LoaderBase>();
// 下载 Json
minecraftName = minecraftName.Replace("∞", "infinite"); // 放在 ID 后面避免影响实例文件夹名称
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.ObtainLabyModClientUrl"), task =>
{
// 启动依赖实例的下载
if (fixLibrary)
McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, minecraftName,
$"https://releases.r2.labymod.net/api/v1/download/manifest/labymod4/{labyModChannel}/{minecraftName}/{labyModCommitRef}.json");
task.Progress = 0.5d;
// 构造文件请求
task.output = new List<DownloadFile>
{
new(
new[]
{
$"https://releases.r2.labymod.net/api/v1/download/manifest/labymod4/{labyModChannel}/{minecraftName}/{labyModCommitRef}.json"
}, Path.Combine(versionFolder, id + ".json"), new ModBase.FileChecker(isJson: true))
};
task.Progress = 1d;
})
{
ProgressWeight = 2d
});
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLoaderLibraries", "Fabric"),
new List<DownloadFile>())
{ ProgressWeight = 10d });
// 下载支持库
if (fixLibrary)
{
loaders.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeLabyModLibraries"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder)))
{ ProgressWeight = 1d, show = false });
loaders.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLoaderLibraries", "LabyMod"),
new List<DownloadFile>())
{ ProgressWeight = 8d });
}
return loaders;
}
/// <summary>
/// 获取下载某个 Minecraft 实例的加载器列表。
/// 它必须安装到 PathMcFolder,但是可以自定义实例名(不过自定义的实例名不会修改 Json 中的 id 项)。
/// </summary>
private static List<ModLoader.LoaderBase> McDownloadLabyModClientLoader(string id, string labyChannel,
string labyCommitRef, string versionName = null)
{
versionName = versionName ?? id;
var versionFolder = Path.Combine(ModFolder.mcFolderSelected, "versions", versionName) + @"\";
var loaders = new List<ModLoader.LoaderBase>();
// 下载支持库文件
var loadersLib = new List<ModLoader.LoaderBase>();
loadersLib.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeVanillaAndLabyModLibrariesSide"), task =>
{
ModBase.WaitForFileReady(Path.Combine(versionFolder, versionName + ".json"));
ModBase.Log("[Download] 开始分析原版与 LabyMod 支持库文件:" + versionFolder);
task.output = ModLibrary.McLibNetFilesFromInstance(new McInstance(versionFolder));
})
{
ProgressWeight = 1d,
show = false
});
loadersLib.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadVanillaAndLabyModLibrariesSide"),
new List<DownloadFile>())
{ ProgressWeight = 13d, show = false });
loaders.Add(new ModLoader.LoaderCombo<string>(mcDownloadClientLibName, loadersLib)
{ block = false, ProgressWeight = 14d });
// 下载资源文件
var loadersAssets = new List<ModLoader.LoaderBase>();
loadersAssets.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeAssetsIndex.Side"), task =>
{
try
{
var version = new McInstance(versionFolder);
task.output = new List<DownloadFile> { ModDownload.DlClientAssetIndexGet(version) };
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.AssetIndexAnalysisFailed"), ex);
}
// 顺手添加 Json 项目
try
{
var versionJson = (JsonObject)ModBase.GetJson(ModBase.ReadFile(Path.Combine(versionFolder, versionName + ".json")));
versionJson.Add("clientVersion", id);
ModBase.WriteFile(Path.Combine(versionFolder, versionName + ".json"), versionJson.ToString());
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.AddClientVersionFailed"), ex);
}
})
{
ProgressWeight = 1d,
show = false
});
loadersAssets.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadAssetsIndex.Side"),
new List<DownloadFile>())
{ ProgressWeight = 3d, show = false });
loadersAssets.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeRequiredAssets.Side"), task =>
{
ModLoader.LoaderBase argprogressFeed = task;
task.output =
ModAssets.McAssetsFixList(new McInstance(versionFolder), true, ref argprogressFeed);
task = (ModLoader.LoaderTask<string, List<DownloadFile>>)argprogressFeed;
})
{
ProgressWeight = 3d,
show = false
});
loadersAssets.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadAssets.Side"),
new List<DownloadFile>())
{ ProgressWeight = 14d, show = false });
loaders.Add(
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.DownloadVanillaAssets"),
loadersAssets) { block = false, ProgressWeight = 21d });
return loaders;
}
#endregion
#region LabyMod 下载菜单
public static MyListItem LabyModDownloadListItem(JsonObject entry, MyListItem.ClickEventHandler onClick)
{
// 建立控件
var newItem = new MyListItem
{
Title = entry["version"] + " " + (entry["channel"].ToString().Contains("snapshot") ? Lang.Text("Download.Version.Type.Snapshot") : Lang.Text("Download.Version.Type.Stable")),
SnapsToDevicePixels = true,
Height = 42d,
Type = MyListItem.CheckType.Clickable,
Tag = entry,
Info = entry["channel"].ToString().Contains("snapshot") ? Lang.Text("Download.Version.Type.Snapshot") : Lang.Text("Download.Version.Type.Stable"),
Logo = ModBase.pathImage + "Blocks/LabyMod.png"
};
newItem.Click += onClick;
newItem.ContentHandler = LabyModContMenuBuild;
// 结束
return newItem;
}
private static void LabyModContMenuBuild(object sender, EventArgs e)
{
var btnSave = new MyIconButton { SvgIcon = "lucide/save", ToolTip = Lang.Text("Download.Version.SaveAs") };
ToolTipService.SetPlacement(btnSave, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnSave, 30d);
ToolTipService.SetHorizontalOffset(btnSave, 2d);
btnSave.Click += (a, b) => LabyModSave_Click(a, (dynamic)b);
var btnInfo = new MyIconButton { LogoScale = 1.05d, SvgIcon = "lucide/info", ToolTip = Lang.Text("Download.Version.Changelog") };
ToolTipService.SetPlacement(btnInfo, PlacementMode.Center);
ToolTipService.SetVerticalOffset(btnInfo, 30d);
ToolTipService.SetHorizontalOffset(btnInfo, 2d);
btnInfo.Click += (a, b) => LabyModLog_Click(a, (dynamic)b);
((dynamic)sender).Buttons = new[] { btnSave, btnInfo };
}
private static void LabyModLog_Click(object sender, RoutedEventArgs e)
{
ModBase.OpenWebsite("https://www.labymod.net/zh_Hans/download");
}
private static void LabyModSave_Click(object sender, RoutedEventArgs e)
{
JsonObject version;
if (((dynamic)sender).Tag is not null)
version = (JsonObject)((dynamic)sender).Tag;
else if (((dynamic)sender).Parent.Tag is not null)
version = (JsonObject)((dynamic)sender).Parent.Tag;
else
version = (JsonObject)((dynamic)sender).Parent.Parent.Tag;
if ((string)version["channel"] == "snapshot")
McDownloadLabyModSnapshotLoaderSave();
else
McDownloadLabyModProductionLoaderSave();
}
#endregion
#region 合并安装
/// <summary>
/// 安装请求。
/// </summary>
public class McInstallRequest
{
/// <summary>
/// 欲下载的 Cleanroom。
/// </summary>
public ModDownload.DlCleanroomListEntry cleanroomEntry = null;
// 若要下载 Cleanroom,则需要在下面两项中完成至少一项
/// <summary>
/// 欲下载的 Cleanroom 版本名。
/// </summary>
public string cleanroomVersion;
/// <summary>
/// 欲下载的 Fabric API 信息。
/// </summary>
public ModComp.CompFile fabricApi = null;
/// <summary>
/// 欲下载的 Fabric Loader 版本名。
/// </summary>
public string fabricVersion = null;
/// <summary>
/// 欲下载的 Forge。
/// </summary>
public ModDownload.DlForgeVersionEntry forgeEntry = null;
// 若要下载 Forge,则需要在下面两项中完成至少一项
/// <summary>
/// 欲下载的 Forge 版本名。接受例如 36.1.4 / 14.23.5.2859 / 1.19-41.1.0 的输入。
/// </summary>
public string forgeVersion;
/// <summary>
/// 欲下载的 LabyMod 通道。
/// </summary>
public string labyModChannel = null;
/// <summary>
/// 欲下载的 LabyMod 版本。
/// </summary>
public string labyModCommitRef = null;
/// <summary>
/// 欲下载的 Legacy Fabric API 信息。
/// </summary>
public ModComp.CompFile legacyFabricApi = null;
/// <summary>
/// 欲下载的 Legacy Fabric Loader 版本名。
/// </summary>
public string legacyFabricVersion = null;
/// <summary>
/// 欲下载的 LiteLoader 详细信息。
/// </summary>
public ModDownload.DlLiteLoaderListEntry liteLoaderEntry = null;
/// <summary>
/// 可选。欲下载的 Minecraft Json 地址。
/// </summary>
public string minecraftJson = null;
/// <summary>
/// 必填。欲下载的 Minecraft 的版本名。
/// </summary>
public string minecraftName = null;
/// <summary>
/// 若 MMC 整合包安装包含特殊参数,则填写此项。
/// </summary>
public ModModpack.MMCPackInfo mmcPackInfo = null;
/// <summary>
/// 欲下载的 NeoForge。
/// </summary>
public ModDownload.DlNeoForgeListEntry neoForgeEntry = null;
// 若要下载 NeoForge,则需要在下面两项中完成至少一项
/// <summary>
/// 欲下载的 NeoForge 版本名。
/// </summary>
public string neoForgeVersion;
/// <summary>
/// 欲下载的 OptiFabric 信息。
/// </summary>
public ModComp.CompFile optiFabric = null;
/// <summary>
/// 欲下载的 OptiFine 详细信息。
/// </summary>
public ModDownload.DlOptiFineListEntry optiFineEntry;
// 若要下载 OptiFine,则需要在下面两项中完成至少一项
/// <summary>
/// 欲下载的 OptiFine 版本名。例如 HD_U_F6_pre1。
/// </summary>
public string optiFineVersion;
/// <summary>
/// 必填。安装目标文件夹。
/// </summary>
public string targetInstanceFolder;
/// <summary>
/// 必填。安装目标实例名称。
/// </summary>
public string targetInstanceName;
}
/// <summary>
/// 在加载器状态改变后显示一条提示。
/// 不会进行任何其他操作。
/// </summary>
public static void LoaderStateChangedHintOnly(object loaderObj)
{
var loader = (ModLoader.LoaderBase)loaderObj;
switch (loader.State)
{
case ModBase.LoadState.Finished:
HintService.Hint($"{loader.name}{Lang.Text("Common.Status.Success")}", HintType.Success);
break;
case ModBase.LoadState.Failed:
HintService.Hint($"{loader.name}{Lang.Text("Common.Status.Failure")}{loader.Error.Message}", HintType.Error);
break;
case ModBase.LoadState.Aborted:
HintService.Hint($"{loader.name}{Lang.Text("Common.Status.Cancelled")}");
break;
}
}
/// <summary>
/// 安装加载器状态改变后进行提示和重载文件夹列表的方法。
/// </summary>
public static void McInstallState(object loaderObj)
{
var loader = (ModLoader.LoaderBase)loaderObj;
var combo = (ModLoader.LoaderCombo)loader;
switch (loader.State)
{
case ModBase.LoadState.Finished:
{
if (Config.Download.AutoSelectInstance)
{
var versionName = loader.name;
ModBase.WriteIni(ModFolder.mcFolderSelected + "PCL.ini", "Version",
versionName.Remove(versionName.Length - 3, 3));
}
ModBase.WriteIni(ModFolder.mcFolderSelected + "PCL.ini", "InstanceCache",
""); // 清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存)
ModBase.DeleteDirectory($"{combo.input}PCLInstallBackups\\");
HintService.Hint($"{loader.name}{Lang.Text("Common.Status.Success")}",
HintType.Success);
break;
}
case ModBase.LoadState.Failed:
{
HintService.Hint(
$"{loader.name}{Lang.Text("Common.Status.Failure")}{loader.Error.Message}",
HintType.Error);
break;
}
case ModBase.LoadState.Aborted:
{
HintService.Hint($"{loader.name}{Lang.Text("Common.Status.Cancelled")}");
break;
}
case ModBase.LoadState.Loading:
{
return; // 不重新加载实例列表
}
}
if (loader.State != ModBase.LoadState.Finished &&
Directory.Exists(
$"{combo.input}PCLInstallBackups\\")) // 实例修改失败回滚
{
ModBase.CopyDirectory(
$"{combo.input}PCLInstallBackups\\",
(string)combo.input);
File.Delete($"{combo.input}.pclignore");
ModBase.DeleteDirectory(
$"{combo.input}PCLInstallBackups\\");
}
else
{
McInstallFailedClearFolder(loader);
}
ModLoader.LoaderFolderRun(ModInstanceList.mcInstanceListLoader, ModFolder.mcFolderSelected,
ModLoader.LoaderFolderRunType.ForceRun, 1, @"versions\");
}
public static void McInstallFailedClearFolder(object loader)
{
try
{
Thread.Sleep(1000); // 防止存在尚未完全释放的文件,导致清理失败(例如整合包安装)
if (((ModLoader.LoaderBase)loader).State == ModBase.LoadState.Failed ||
((ModLoader.LoaderBase)loader).State == ModBase.LoadState.Aborted)
{
// 删除实例文件夹
ModBase.Log($"[Download] 由于下载失败或取消,清理实例文件夹:{((ModLoader.LoaderCombo)loader).input}", ModBase.LogLevel.Developer);
var instancePath = (string)((ModLoader.LoaderCombo)loader).input;
((DynamicCacheConfigStorage)ConfigService.GetProvider(ConfigSource.GameInstance)).InvalidateCache(instancePath);
ModBase.DeleteDirectory(instancePath);
}
}
catch (Exception ex)
{
ModBase.Log(ex, "下载失败或取消后清理实例文件夹失败");
}
}
private const string mcInstallDefaultType = "安装";
/// <summary>
/// 进行合并安装。返回是否已经开始安装(例如如果没有安装 Java 则会进行提示并返回 False)
/// </summary>
public static bool McInstall(McInstallRequest request, string type = mcInstallDefaultType)
{
try
{
var subLoaders = McInstallLoader(request, ignoreDump: type != mcInstallDefaultType);
if (subLoaders is null)
return false;
var loader = new ModLoader.LoaderCombo<string>(request.targetInstanceName + " " + type, subLoaders)
{ OnStateChanged = McInstallState };
// 启动
loader.Start(request.targetInstanceFolder);
ModLoader.LoaderTaskbarAdd(loader);
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
ModMain.frmMain.BtnExtraDownload.Ribble();
return true;
}
catch (ModBase.CancelledException ex)
{
return false;
}
catch (Exception ex)
{
ModBase.Log(
ex,
"开始合并安装失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Download.Error.OperationFailed"));
try
{
if (Directory.Exists(request.targetInstanceFolder))
{
var files = Directory.GetFiles(request.targetInstanceFolder);
var dirs = Directory.GetDirectories(request.targetInstanceFolder);
if (files.Length <= 1 && dirs.Length == 0)
{
((DynamicCacheConfigStorage)ConfigService.GetProvider(ConfigSource.GameInstance))
.InvalidateCache(request.targetInstanceFolder);
ModBase.DeleteDirectory(request.targetInstanceFolder);
}
}
}
catch (Exception innerEx)
{
ModBase.Log(innerEx, "清理未完成的实例文件夹失败");
}
return false;
}
}
/// <summary>
/// 获取合并安装加载器列表,并进行前期的缓存清理与 Java 检查工作。
/// </summary>
/// <exception cref="ModBase.CancelledException" />
public static List<ModLoader.LoaderBase> McInstallLoader(McInstallRequest request, bool dontFixLibraries = false,
bool ignoreDump = false)
{
// 获取缓存目录(安装 Mod 加载器的文件夹不能包含空格)
var tempMcFolder = ModMain.RequestTaskTempFolder(request.optiFineEntry is not null ||
request.forgeEntry is not null ||
request.neoForgeEntry is not null);
// 获取参数
var instanceFolder = Path.Combine(ModFolder.mcFolderSelected, "versions", request.targetInstanceName);
if (Directory.Exists(tempMcFolder))
ModBase.DeleteDirectory(tempMcFolder);
string optiFineFolder = null;
if (request.optiFineVersion is not null)
{
if (request.optiFineVersion.Contains("_HD_U_"))
request.optiFineVersion = "HD_U_" + request.optiFineVersion.AfterLast("_HD_U_"); // #735
request.optiFineEntry = new ModDownload.DlOptiFineListEntry
{
DisplayName = request.minecraftName + " " + request.optiFineVersion.Replace("HD_U_", "")
.Replace("_", "").Replace("pre", " pre"),
Inherit = request.minecraftName,
IsPreview = request.optiFineVersion.ContainsF("pre", true),
NameVersion = request.minecraftName + "-OptiFine_" + request.optiFineVersion,
NameFile = (request.optiFineVersion.ContainsF("pre", true) ? "preview_" : "") + "OptiFine_" +
request.minecraftName + "_" + request.optiFineVersion + ".jar"
};
}
if (request.optiFineEntry is not null)
optiFineFolder = Path.Combine(tempMcFolder, "versions", request.optiFineEntry.NameVersion);
string forgeFolder = null;
if (request.forgeEntry is not null)
request.forgeVersion = request.forgeVersion ?? request.forgeEntry.VersionName;
if (request.forgeVersion is not null)
forgeFolder = Path.Combine(tempMcFolder, "versions", "forge-" + request.forgeVersion);
string neoForgeFolder = null;
if (request.neoForgeEntry is not null)
request.neoForgeVersion = request.neoForgeVersion ?? request.neoForgeEntry.VersionName;
if (request.neoForgeVersion is not null)
neoForgeFolder = Path.Combine(tempMcFolder, "versions", "neoforge-" + request.neoForgeVersion);
string cleanroomFolder = null;
if (request.cleanroomEntry is not null)
request.cleanroomVersion = request.cleanroomVersion ?? request.cleanroomEntry.VersionName;
if (request.cleanroomVersion is not null)
cleanroomFolder = Path.Combine(tempMcFolder, "versions", "cleanroom-" + request.cleanroomVersion);
string fabricFolder = null;
if (request.fabricVersion is not null)
fabricFolder = Path.Combine(tempMcFolder, "versions", "fabric-loader-" + request.fabricVersion + "-" +
request.minecraftName);
string legacyFabricFolder = null;
if (request.legacyFabricVersion is not null)
legacyFabricFolder = Path.Combine(tempMcFolder, "versions", "legacy-fabric-loader-" + request.legacyFabricVersion + "-" +
request.minecraftName);
string labyModFolder = null;
if (request.labyModCommitRef is not null)
labyModFolder = Path.Combine(tempMcFolder, "versions", "labymod-" + request.labyModCommitRef + "-" +
request.minecraftName);
string liteLoaderFolder = null;
if (request.liteLoaderEntry is not null)
liteLoaderFolder = Path.Combine(tempMcFolder, "versions", request.minecraftName + "-LiteLoader");
// 判断 OptiFine 是否作为 Mod 进行下载
var modable = request.fabricVersion is not null || request.legacyFabricVersion is not null ||
request.forgeEntry is not null || request.neoForgeEntry is not null ||
request.liteLoaderEntry is not null;
var modsTempFolder = Path.Combine(tempMcFolder, "mods") + @"\";
var optiFineAsMod = request.optiFineEntry is not null && modable; // 选择了 OptiFine 与任意 Mod 加载器
if (optiFineAsMod)
{
ModBase.Log("[Download] OptiFine 将作为 Mod 进行下载");
if (request.liteLoaderEntry is not null)
optiFineFolder = CombineCacheSubfolder(modsTempFolder, request.minecraftName);
else
optiFineFolder = modsTempFolder;
}
// 记录日志
if (optiFineFolder is not null)
ModBase.Log("[Download] OptiFine 缓存:" + optiFineFolder);
if (forgeFolder is not null)
ModBase.Log("[Download] Forge 缓存:" + forgeFolder);
if (neoForgeFolder is not null)
ModBase.Log("[Download] NeoForge 缓存:" + neoForgeFolder);
if (cleanroomFolder is not null)
ModBase.Log("[Download] Cleanroom 缓存:" + cleanroomFolder);
if (fabricFolder is not null)
ModBase.Log("[Download] Fabric 缓存:" + fabricFolder);
if (legacyFabricFolder is not null)
ModBase.Log("[Download] LegacyFabric 缓存:" + legacyFabricFolder);
if (labyModFolder is not null)
ModBase.Log("[Download] LabyMod 缓存:" + labyModFolder);
if (liteLoaderFolder is not null)
ModBase.Log("[Download] LiteLoader 缓存:" + liteLoaderFolder);
ModBase.Log("[Download] 对应的原版版本:" + request.minecraftName);
// 重复实例检查
if (File.Exists(Path.Combine(instanceFolder, request.targetInstanceName + ".json")) && !ignoreDump)
{
HintService.Hint(Lang.Text("Minecraft.Download.Error.InstanceAlreadyExists", request.targetInstanceName, ""),
HintType.Error);
throw new ModBase.CancelledException();
}
var loaderList = new List<ModLoader.LoaderBase>();
// 添加忽略标识
loaderList.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Minecraft.Download.Stage.AddIgnoreFlag"),
_ => ModBase.WriteFile(Path.Combine(instanceFolder, ".pclignore"), "用于临时地在 PCL 的实例列表中屏蔽此实例。"))
{ show = false, block = false });
// Fabric API
if (request.fabricApi is not null)
loaderList.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadFabricApi"),
new List<DownloadFile>
{
request.fabricApi.ToNetFile(modsTempFolder, ModComp.DownloadReason.Dependency, null,
ModComp.CompLoaderType.Fabric)
})
{ ProgressWeight = 3d, block = false });
// LegacyFabric API
if (request.legacyFabricApi is not null)
loaderList.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLegacyFabricApi"),
new List<DownloadFile>
{
request.legacyFabricApi.ToNetFile(modsTempFolder, ModComp.DownloadReason.Dependency, null,
ModComp.CompLoaderType.Fabric)
})
{ ProgressWeight = 3d, block = false });
// OptiFabric
if (request.optiFabric is not null)
loaderList.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadOptiFabric"),
new List<DownloadFile>
{
request.optiFabric.ToNetFile(modsTempFolder, ModComp.DownloadReason.Dependency, null,
ModComp.CompLoaderType.Fabric)
})
{ ProgressWeight = 3d, block = false });
// LabyMod
if (request.labyModCommitRef is not null)
{
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "LabyMod", request.labyModCommitRef),
McDownloadLabyModLoader(request.labyModCommitRef, request.labyModChannel, request.minecraftName,
tempMcFolder, false)) { show = false, ProgressWeight = 10d, block = true });
goto LabyModSkip;
}
// 原版
var clientLoader = new ModLoader.LoaderCombo<string>(
Lang.Text(
"Minecraft.Download.Stage.LoaderDownloadCombo",
Lang.Text("Minecraft.Version.Vanilla"),
request.minecraftName
),
McDownloadClientLoader(
request.minecraftName, request.minecraftJson, request.targetInstanceName
)
)
{
show = false,
ProgressWeight = 39d,
block = request.forgeVersion is null && request.neoForgeVersion is null && request.optiFineEntry is null &&
request.fabricVersion is null && request.liteLoaderEntry is null &&
request.cleanroomEntry is null && request.legacyFabricVersion is null
};
loaderList.Add(clientLoader);
// OptiFine
if (request.optiFineEntry is not null)
{
if (optiFineAsMod)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "OptiFine",
request.optiFineEntry.DisplayName),
McDownloadOptiFineSaveLoader(request.optiFineEntry,
Path.Combine(optiFineFolder, request.optiFineEntry.NameFile)))
{
show = false,
ProgressWeight = 16d,
block = request.forgeVersion is null && request.neoForgeVersion is null &&
request.fabricVersion is null && request.liteLoaderEntry is null
});
else
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "OptiFine",
request.optiFineEntry.DisplayName),
McDownloadOptiFineLoader(request.optiFineEntry, tempMcFolder, clientLoader,
request.targetInstanceFolder, false))
{
show = false,
ProgressWeight = 24d,
block = request.forgeVersion is null && request.neoForgeVersion is null &&
request.fabricVersion is null && request.liteLoaderEntry is null
});
}
// Forge
if (request.forgeVersion is not null)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "Forge", request.forgeVersion),
McDownloadForgelikeLoader(ModDownload.DlForgelikeEntry.ForgelikeType.Forge, request.forgeVersion, "forge-" + request.forgeVersion,
request.minecraftName, request.forgeEntry, tempMcFolder, clientLoader,
request.targetInstanceFolder))
{
show = false, ProgressWeight = 25d,
block = request.fabricVersion is null && request.liteLoaderEntry is null &&
request.neoForgeEntry is null
});
// NeoForge
if (request.neoForgeVersion is not null)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "NeoForge", request.neoForgeVersion),
McDownloadForgelikeLoader(ModDownload.DlForgelikeEntry.ForgelikeType.NeoForge, request.neoForgeVersion, "neoforge-" + request.neoForgeVersion,
request.minecraftName, request.neoForgeEntry, tempMcFolder, clientLoader,
request.targetInstanceFolder))
{
show = false, ProgressWeight = 25d,
block = request.forgeEntry is null && request.fabricVersion is null && request.liteLoaderEntry is null
});
// Cleanroom
if (request.cleanroomVersion is not null)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "Cleanroom", request.cleanroomVersion),
McDownloadForgelikeLoader(ModDownload.DlForgelikeEntry.ForgelikeType.Cleanroom, request.cleanroomVersion,
"cleanroom-" + request.cleanroomVersion, request.minecraftName, request.cleanroomEntry,
tempMcFolder, clientLoader, request.targetInstanceFolder))
{
show = false, ProgressWeight = 25d,
block = request.forgeEntry is null && request.fabricVersion is null && request.liteLoaderEntry is null
});
// LiteLoader
if (request.liteLoaderEntry is not null)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "LiteLoader", request.minecraftName),
McDownloadLiteLoaderLoader(request.liteLoaderEntry, tempMcFolder, clientLoader, false))
{
show = false,
ProgressWeight = 1d,
block = request.fabricVersion is null
});
// Fabric
if (request.fabricVersion is not null)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "Fabric", request.fabricVersion),
McDownloadFabricLoader(request.fabricVersion, request.minecraftName, tempMcFolder, false))
{
show = false,
ProgressWeight = 2d,
block = true
});
// LegacyFabric
if (request.legacyFabricVersion is not null)
loaderList.Add(new ModLoader.LoaderCombo<string>(
Lang.Text("Minecraft.Download.Stage.LoaderDownloadCombo", "Legacy Fabric", request.legacyFabricVersion),
McDownloadLegacyFabricLoader(request.legacyFabricVersion, request.minecraftName, tempMcFolder, false))
{
show = false,
ProgressWeight = 2d,
block = true
});
LabyModSkip: ;
// 合并安装
loaderList.Add(new ModLoader.LoaderTask<string, string>(Lang.Text("Minecraft.Download.Stage.InstallGame"),
task =>
{
// 合并 JSON
MergeJson(instanceFolder, instanceFolder, optiFineFolder, optiFineAsMod, forgeFolder, request.forgeVersion,
neoForgeFolder, request.neoForgeVersion, cleanroomFolder, request.cleanroomVersion, fabricFolder,
labyModFolder, request.labyModChannel, liteLoaderFolder, request.mmcPackInfo,
legacyFabricFolder);
task.Progress = 0.2d;
// 迁移文件
if (Directory.Exists(Path.Combine(tempMcFolder, "libraries")))
ModBase.CopyDirectory(Path.Combine(tempMcFolder, "libraries"), Path.Combine(ModFolder.mcFolderSelected, "libraries"));
task.Progress = 0.8d;
// 创建 Mod 和资源包文件夹
var modsFolder = Path.Combine(new McInstance(instanceFolder).PathIndie, "mods"); // 版本隔离信息在此时被决定
if (Directory.Exists(modsTempFolder))
{
ModBase.CopyDirectory(modsTempFolder, modsFolder);
}
else if (modable)
{
Directory.CreateDirectory(modsFolder);
ModBase.Log("[Download] 自动创建 Mod 文件夹:" + modsFolder);
}
var resourcepacksFolder = Path.Combine(new McInstance(instanceFolder).PathIndie, "resourcepacks");
Directory.CreateDirectory(resourcepacksFolder);
ModBase.Log("[Download] 自动创建资源包文件夹:" + resourcepacksFolder);
})
{
ProgressWeight = 2d,
block = true
});
// 补全文件
if (!dontFixLibraries && (request.optiFineEntry is not null ||
(request.forgeVersion is not null &&
Convert.ToDouble(request.forgeVersion.BeforeFirst(".")) >= 20d) ||
request.neoForgeVersion is not null || request.fabricVersion is not null ||
request.cleanroomVersion is not null ||
request.liteLoaderEntry is not null || request.labyModCommitRef is not null))
{
var loadersLib = new List<ModLoader.LoaderBase>();
if (request.labyModCommitRef is not null)
{
var labyModClientLoader = new ModLoader.LoaderCombo<string>(
Lang.Text(
"Minecraft.Download.Stage.LoaderDownloadCombo",
Lang.Text("Minecraft.Version.Vanilla"), request.minecraftName
),
McDownloadLabyModClientLoader(
request.minecraftName, request.labyModChannel,
request.labyModCommitRef, request.targetInstanceName
)
)
{
show = false, ProgressWeight = 39d, block = false
};
loaderList.Add(labyModClientLoader);
}
else
{
loadersLib.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
Lang.Text("Minecraft.Download.Stage.AnalyzeGameLibrariesSide"),
task => task.output =
ModLibrary.McLibNetFilesFromInstance(new McInstance(instanceFolder)))
{ ProgressWeight = 1d, show = false });
loadersLib.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadGameLibrariesSide"),
new List<DownloadFile>())
{ ProgressWeight = 7d, show = false });
loaderList.Add(
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.DownloadGameLibraries"),
loadersLib) { ProgressWeight = 8d });
}
}
// 删除忽略标识
loaderList.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Minecraft.Download.Stage.DeleteIgnoreFlag"),
_ => File.Delete(Path.Combine(instanceFolder, ".pclignore")))
{ show = false });
// 总加载器
return loaderList;
}
/// <summary>
/// 将多个实例 JSON 进行合并,如果目标已存在则直接覆盖。失败会抛出异常。
/// </summary>
private static void MergeJson(string outputFolder, string minecraftFolder, string optiFineFolder = null,
bool optiFineAsMod = false, string forgeFolder = null, string forgeVersion = null, string neoForgeFolder = null,
string neoForgeVersion = null, string cleanroomFolder = null, string cleanroomVersion = null,
string fabricFolder = null, string labyModFolder = null,
string labyModChannel = null, string liteLoaderFolder = null, ModModpack.MMCPackInfo mMCPackInfo = null,
string legacyFabricFolder = null)
{
ModBase.Log("[Download] 开始进行实例合并,输出:" + outputFolder + "Minecraft" + minecraftFolder +
(optiFineFolder is not null ? "OptiFine" + optiFineFolder : "") +
(forgeFolder is not null ? "Forge" + forgeFolder : "") +
(neoForgeFolder is not null ? "NeoForge" + neoForgeFolder : "") +
(cleanroomFolder is not null ? "Cleanroom" + cleanroomFolder : "") +
(liteLoaderFolder is not null ? "LiteLoader" + liteLoaderFolder : "") +
(fabricFolder is not null ? "Fabric" + fabricFolder : "") +
(legacyFabricFolder is not null ? "LegacyFabric" + legacyFabricFolder : "") +
(labyModFolder is not null ? "LabyMod" + labyModFolder : ""));
Directory.CreateDirectory(outputFolder);
var hasOptiFine = optiFineFolder is not null && !optiFineAsMod;
var hasForge = forgeFolder is not null;
var hasLegacyFabric = legacyFabricFolder is not null;
var hasNeoForge = neoForgeFolder is not null;
var hasCleanroom = cleanroomFolder is not null;
var hasLiteLoader = liteLoaderFolder is not null;
var hasFabric = fabricFolder is not null;
var hasLabyMod = labyModFolder is not null;
string outputName;
string minecraftName;
string optiFineName;
string forgeName;
string neoForgeName;
string cleanroomName;
string liteLoaderName;
string fabricName;
string legacyFabricName;
string labyModName;
string outputJsonPath;
string minecraftJsonPath;
string optiFineJsonPath = null;
string forgeJsonPath = null;
string neoForgeJsonPath = null;
string cleanroomJsonPath = null;
string liteLoaderJsonPath = null;
string fabricJsonPath = null;
string labyModJsonPath = null;
string legacyFabricJsonPath = null;
string outputJar;
string minecraftJar;
#region 初始化路径信息
if (!outputFolder.EndsWithF(@"\"))
outputFolder += @"\";
outputName = ModBase.GetFolderNameFromPath(outputFolder);
outputJsonPath = Path.Combine(outputFolder, outputName + ".json");
outputJar = Path.Combine(outputFolder, outputName + ".jar");
if (!minecraftFolder.EndsWithF(@"\"))
minecraftFolder += @"\";
minecraftName = ModBase.GetFolderNameFromPath(minecraftFolder);
minecraftJsonPath = Path.Combine(minecraftFolder, minecraftName + ".json");
minecraftJar = Path.Combine(minecraftFolder, minecraftName + ".jar");
if (hasOptiFine)
{
if (!optiFineFolder.EndsWithF(@"\"))
optiFineFolder += @"\";
optiFineName = ModBase.GetFolderNameFromPath(optiFineFolder);
optiFineJsonPath = Path.Combine(optiFineFolder, optiFineName + ".json");
}
if (hasForge)
{
if (!forgeFolder.EndsWithF(@"\"))
forgeFolder += @"\";
forgeName = ModBase.GetFolderNameFromPath(forgeFolder);
forgeJsonPath = Path.Combine(forgeFolder, forgeName + ".json");
}
if (hasNeoForge)
{
if (!neoForgeFolder.EndsWithF(@"\"))
neoForgeFolder += @"\";
neoForgeName = ModBase.GetFolderNameFromPath(neoForgeFolder);
neoForgeJsonPath = Path.Combine(neoForgeFolder, neoForgeName + ".json");
}
if (hasCleanroom)
{
if (!cleanroomFolder.EndsWithF(@"\"))
cleanroomFolder += @"\";
cleanroomName = ModBase.GetFolderNameFromPath(cleanroomFolder);
cleanroomJsonPath = Path.Combine(cleanroomFolder, cleanroomName + ".json");
}
if (hasLiteLoader)
{
if (!liteLoaderFolder.EndsWithF(@"\"))
liteLoaderFolder += @"\";
liteLoaderName = ModBase.GetFolderNameFromPath(liteLoaderFolder);
liteLoaderJsonPath = Path.Combine(liteLoaderFolder, liteLoaderName + ".json");
}
if (hasFabric)
{
if (!fabricFolder.EndsWithF(@"\"))
fabricFolder += @"\";
fabricName = ModBase.GetFolderNameFromPath(fabricFolder);
fabricJsonPath = Path.Combine(fabricFolder, fabricName + ".json");
}
if (hasLegacyFabric)
{
if (!legacyFabricFolder.EndsWithF(@"\"))
legacyFabricFolder += @"\";
legacyFabricName = ModBase.GetFolderNameFromPath(legacyFabricFolder);
legacyFabricJsonPath = Path.Combine(legacyFabricFolder, legacyFabricName + ".json");
}
if (hasLabyMod)
{
if (!labyModFolder.EndsWithF(@"\"))
labyModFolder += @"\";
labyModName = ModBase.GetFolderNameFromPath(labyModFolder);
labyModJsonPath = Path.Combine(labyModFolder, labyModName + ".json");
}
#endregion
JsonObject outputJson;
JsonObject minecraftJson = null;
JsonObject optiFineJson = null;
JsonObject forgeJson = null;
JsonObject neoForgeJson = null;
JsonObject legacyFabricJson = null;
JsonObject cleanroomJson = null;
JsonObject liteLoaderJson = null;
JsonObject fabricJson = null;
JsonObject labyModJson = null;
#region 读取文件并检查文件是否合规
var minecraftJsonText = ModBase.ReadFile(minecraftJsonPath);
if (!hasLabyMod)
{
if (!minecraftJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "Minecraft", minecraftJsonPath,
minecraftJsonText.Substring(0, Math.Min(minecraftJsonText.Length, 1000))));
minecraftJson = (JsonObject)ModBase.GetJson(minecraftJsonText);
}
if (hasOptiFine)
{
var optiFineJsonText = ModBase.ReadFile(optiFineJsonPath);
if (!optiFineJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "OptiFine", optiFineJsonPath,
optiFineJsonText.Substring(0, Math.Min(optiFineJsonText.Length, 1000))));
optiFineJson = (JsonObject)ModBase.GetJson(optiFineJsonText);
}
if (hasForge)
{
var forgeJsonText = ModBase.ReadFile(forgeJsonPath);
if (!forgeJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "Forge", forgeJsonPath,
forgeJsonText.Substring(0, Math.Min(forgeJsonText.Length, 1000))));
forgeJson = (JsonObject)ModBase.GetJson(forgeJsonText);
}
if (hasNeoForge)
{
var neoForgeJsonText = ModBase.ReadFile(neoForgeJsonPath);
if (!neoForgeJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "NeoForge", neoForgeJsonPath,
neoForgeJsonText.Substring(0, Math.Min(neoForgeJsonText.Length, 1000))));
neoForgeJson = (JsonObject)ModBase.GetJson(neoForgeJsonText);
}
if (hasCleanroom)
{
var cleanroomJsonText = ModBase.ReadFile(cleanroomJsonPath);
if (!cleanroomJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "Cleanroom", cleanroomJsonPath,
cleanroomJsonText.Substring(0, Math.Min(cleanroomJsonText.Length, 1000))));
cleanroomJson = (JsonObject)ModBase.GetJson(cleanroomJsonText);
}
if (hasLiteLoader)
{
var liteLoaderJsonText = ModBase.ReadFile(liteLoaderJsonPath);
if (!liteLoaderJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "LiteLoader", liteLoaderJsonPath,
liteLoaderJsonText.Substring(0, Math.Min(liteLoaderJsonText.Length, 1000))));
liteLoaderJson = (JsonObject)ModBase.GetJson(liteLoaderJsonText);
}
if (hasFabric)
{
var fabricJsonText = ModBase.ReadFile(fabricJsonPath);
if (!fabricJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "Fabric", fabricJsonPath,
fabricJsonText.Substring(0, Math.Min(fabricJsonText.Length, 1000))));
fabricJson = (JsonObject)ModBase.GetJson(fabricJsonText);
}
if (hasLegacyFabric)
{
var legacyFabricJsonText = ModBase.ReadFile(legacyFabricJsonPath);
if (!legacyFabricJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "Legacy Fabric", fabricJsonPath,
legacyFabricJsonText.Substring(0, Math.Min(legacyFabricJsonText.Length, 1000))));
legacyFabricJson = (JsonObject)ModBase.GetJson(legacyFabricJsonText);
}
if (hasLabyMod)
{
var labyModJsonText = ModBase.ReadFile(labyModJsonPath);
if (!labyModJsonText.StartsWithF("{"))
throw new Exception(Lang.Text("Minecraft.Download.Error.JsonInvalid", "LabyMod", labyModJsonPath,
labyModJsonText.Substring(0, Math.Min(labyModJsonText.Length, 1000))));
labyModJson = (JsonObject)ModBase.GetJson(labyModJsonText);
}
#endregion
#region 处理 JSON 文件
// 获取 minecraftArguments
var allArguments = (minecraftJson is not null ? (minecraftJson["minecraftArguments"] ?? " ").ToString() : " ") +
" " +
(labyModJson is not null ? (labyModJson["minecraftArguments"] ?? " ").ToString() : " ") +
" " +
(optiFineJson is not null ? (optiFineJson["minecraftArguments"] ?? " ").ToString() : " ") +
" " + (forgeJson is not null ? (forgeJson["minecraftArguments"] ?? " ").ToString() : " ") +
" " +
(neoForgeJson is not null ? (neoForgeJson["minecraftArguments"] ?? " ").ToString() : " ") +
" " + (liteLoaderJson is not null
? (liteLoaderJson["minecraftArguments"] ?? " ").ToString()
: " ") + " " + (cleanroomJson is not null
? (cleanroomJson["minecraftArguments"] ?? " ").ToString()
: " ");
// 分割参数字符串
var rawArguments = allArguments.Split(" ").Where(l => !string.IsNullOrEmpty(l)).Select(l => l.Trim()).ToList();
var splitArguments = new List<string>();
for (int i = 0, loopTo = rawArguments.Count - 1; i <= loopTo; i++)
if (rawArguments[i].StartsWithF("-"))
splitArguments.Add(rawArguments[i]);
else if (splitArguments.Any() && splitArguments.Last().StartsWithF("-") &&
!splitArguments.Last().Contains(" "))
splitArguments[splitArguments.Count - 1] = splitArguments.Last() + " " + rawArguments[i];
else
splitArguments.Add(rawArguments[i]);
var realArguments = splitArguments.Distinct().ToList().Join(" ");
// 合并
// 相关讨论见 #2801
if (mMCPackInfo is not null)
{
if (mMCPackInfo.isMinecraftOverrided)
{
ModBase.Log("[Download] 当前实例的 MC 核心已被修改,使用对应的 MMC 整合包参数");
outputJson = mMCPackInfo.overridedJson;
}
else
{
ModBase.Log("[Download] 存在无修改 MC 核心文件的 MMC 整合包信息,应用相关参数");
outputJson = minecraftJson;
// 合并来自 MultiMC 的 JSON
outputJson.Merge(mMCPackInfo.overridedJson);
}
}
else
{
outputJson = minecraftJson;
}
if (hasOptiFine)
{
// 合并 OptiFine
optiFineJson.Remove("releaseTime");
optiFineJson.Remove("time");
outputJson.Merge(optiFineJson);
}
if (hasForge)
if (mMCPackInfo is null || !mMCPackInfo.isForgeOverrided)
{
// 合并 Forge
forgeJson.Remove("releaseTime");
forgeJson.Remove("time");
outputJson.Merge(forgeJson);
}
if (hasNeoForge)
if (mMCPackInfo is null || !mMCPackInfo.isNeoForgeOverrided)
{
// 合并 NeoForge
neoForgeJson.Remove("releaseTime");
neoForgeJson.Remove("time");
outputJson.Merge(neoForgeJson);
}
if (hasCleanroom)
if (mMCPackInfo is null || !mMCPackInfo.isCleanroomOverrided)
{
// 合并 Cleanroom
cleanroomJson.Remove("releaseTime");
cleanroomJson.Remove("time");
outputJson.Merge(cleanroomJson);
}
if (hasLiteLoader)
{
// 合并 LiteLoader
liteLoaderJson.Remove("releaseTime");
liteLoaderJson.Remove("time");
outputJson.Merge(liteLoaderJson);
}
if (hasFabric)
if (mMCPackInfo is null || !mMCPackInfo.isFabricOverrided)
{
// 合并 Fabric
fabricJson.Remove("releaseTime");
fabricJson.Remove("time");
outputJson.Merge(fabricJson);
}
if (hasLegacyFabric)
if (mMCPackInfo is null || !mMCPackInfo.isFabricOverrided)
{
// 合并 Fabric
legacyFabricJson.Remove("releaseTime");
legacyFabricJson.Remove("time");
outputJson.Merge(legacyFabricJson);
}
if (hasLabyMod)
{
// 合并 LabyMod
labyModJson.Remove("releaseTime");
labyModJson.Remove("time");
if (outputJson is null)
outputJson = new JsonObject();
outputJson.Merge(labyModJson);
var labyModLib =
(JsonObject)Requester.FetchJson(
$"https://releases.r2.labymod.net/api/v1/libraries/{labyModChannel}.json", RequestParam.WithRetry);
var labyModCore = (JsonObject)Requester.FetchJson(
$"https://releases.r2.labymod.net/api/v1/manifest/{labyModChannel}/latest.json", RequestParam.WithRetry);
var outputLibraries = new JsonArray();
var isolatedLibraries = new Dictionary<string, bool>();
var minecraftVersion = labyModJson["_minecraftVersion"];
foreach (var Library in labyModLib["isolated_libraries"].AsArray())
if (((JsonArray)Library["versions"]).Contains(minecraftVersion))
isolatedLibraries.Add(Library["name"].ToString(), true);
foreach (var Library in labyModJson["libraries"].AsArray())
{
var regexMatchResult = Library["name"].ToString().RegexSeek(RegexPatterns.CatchLwjglInLib);
if (regexMatchResult is null ||
!isolatedLibraries.Contains(new KeyValuePair<string, bool>(regexMatchResult, true)))
outputLibraries.Add(Library.DeepClone());
}
foreach (var Library in labyModLib["libraries"].AsArray())
{
var libraryUrl = Library?["url"]?.ToString() ?? "";
outputLibraries.Add(new JsonObject
{
["name"] = Library?["name"]?.ToString(),
["downloads"] = new JsonObject
{
["artifact"] = new JsonObject
{
["path"] = libraryUrl.Substring(libraryUrl.LastIndexOfF("https://releases.r2.labymod.net/libraries/") + 42),
["sha1"] = Library?["sha1"]?.ToString(),
["size"] = Library?["size"]?.DeepClone(),
["url"] = libraryUrl
}
}
});
}
var labyModCommitReference = labyModCore["commitReference"]?.ToString() ?? "";
outputLibraries.Add(new JsonObject
{
["name"] = "net.labymod:LabyMod:4",
["downloads"] = new JsonObject
{
["artifact"] = new JsonObject
{
["path"] = "net/labymod/LabyMod/4/LabyMod-4.jar",
["sha1"] = labyModCore["sha1"]?.ToString(),
["size"] = labyModCore["size"]?.DeepClone(),
["url"] = $"https://releases.r2.labymod.net/api/v1/download/labymod4/{labyModChannel}/{labyModCommitReference}.jar"
}
}
});
outputJson["libraries"] = outputLibraries;
outputJson.Add("labymod_data", new JsonObject
{
["channelType"] = labyModChannel,
["commitReference"] = labyModCommitReference,
["version"] = labyModCore["labyModVersion"]?.ToString(),
["versionType"] = "release"
});
}
// 修改
if (realArguments is not null && !string.IsNullOrEmpty(realArguments.Replace(" ", "")))
outputJson["minecraftArguments"] = realArguments;
if (mMCPackInfo is not null && mMCPackInfo.isMcArgsEdited)
outputJson.Remove("minecraftArguments");
outputJson.Remove("_comment_");
outputJson.Remove("inheritsFrom");
outputJson.Remove("jar");
outputJson["id"] = outputName;
#endregion
#region 保存
ModBase.WriteFile(outputJsonPath, outputJson.ToString());
if ((minecraftJar ?? "") != (outputJar ?? "")) // 可能是同一个文件
{
if (File.Exists(outputJar))
File.Delete(outputJar);
ModBase.CopyFile(minecraftJar, outputJar);
}
ModBase.Log("[Download] 实例合并 " + outputName + " 完成");
#endregion
}
#endregion
}