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

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

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

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

4579 lines
206 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Diagnostics;
using System.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
}