4579 lines
206 KiB
C#
4579 lines
206 KiB
C#
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
|
|||
|
|
}
|