using System.Globalization; using System.IO; using System.Net; using System.Text; using System.Text.Json.Nodes; using PCL.Core.App; using PCL.Core.App.Localization; using PCL.Core.IO.Net.Http; using PCL.Core.Utils; using PCL.Network; using PCL.Network.Loaders; namespace PCL; public static class ModDownload { #region DlClient* | Minecraft 客户端 /// /// 返回某 Minecraft 版本对应的原版主 Jar 文件的下载信息,要求对应依赖实例已存在。 /// 失败则抛出异常,不需要下载则返回 Nothing。 /// public static DownloadFile DlClientJarGet(McInstance version, bool returnNothingOnFileUseable) { // 获取底层继承实例 try { while (!string.IsNullOrEmpty(version.InheritInstanceName)) version = new McInstance(version.InheritInstanceName); } catch (Exception ex) { ModBase.Log(ex, "获取底层继承实例失败"); } // 检查 Json 是否标准 if (version.JsonObject["downloads"] is null || version.JsonObject["downloads"]["client"] is null || version.JsonObject["downloads"]["client"]["url"] is null) throw new Exception(Lang.Text("Minecraft.Download.Error.NoJarDownloadInfo", version.Name)); // 检查文件 var checker = new ModBase.FileChecker(1024L, (long)(version.JsonObject["downloads"]["client"]["size"] ?? -1), (string)version.JsonObject["downloads"]["client"]["sha1"]); if (returnNothingOnFileUseable && checker.Check(version.PathInstance + version.Name + ".jar") is null) return null; // 通过校验 // 返回下载信息 var jarUrl = (string)version.JsonObject["downloads"]["client"]["url"]; return new DownloadFile(DlSourceLauncherOrMetaGet(jarUrl), version.PathInstance + version.Name + ".jar", checker); } /// /// 返回某 Minecraft 版本对应的原版主 AssetIndex 文件的下载信息,要求对应依赖实例已存在。 /// 若未找到,则会返回 Legacy 资源文件或 Nothing。 /// public static DownloadFile DlClientAssetIndexGet(McInstance version) { // 获取底层继承实例 while (!string.IsNullOrEmpty(version.InheritInstanceName)) version = new McInstance(version.InheritInstanceName); // 获取信息 var indexInfo = ModAssets.McAssetsGetIndex(version, true, true); var indexAddress = Path.Combine(ModFolder.mcFolderSelected, "assets", "indexes", indexInfo["id"] + ".json"); ModBase.Log("[Download] 实例 " + version.Name + " 对应的资源文件索引为 " + indexInfo["id"]); var indexUrl = (string)(indexInfo["url"] ?? ""); if (string.IsNullOrEmpty(indexUrl)) return null; return new DownloadFile(DlSourceLauncherOrMetaGet(indexUrl), indexAddress, new ModBase.FileChecker(canUseExistsFile: false)); } /// /// 构造补全某 Minecraft 版本的所有文件的加载器列表。失败会抛出异常。 /// public static List DlClientFix(McInstance version, bool checkAssetsHash, AssetsIndexExistsBehaviour assetsIndexBehaviour) { var loaders = new List(); #region 下载支持库文件 if (ModLibrary.ShouldIgnoreFileCheck(version)) { ModBase.Log("[Download] 已跳过所有 Libraries 检查"); } else { var loadersLib = new List { new ModLoader.LoaderTask>( Lang.Text("Minecraft.Download.Stage.AnalyzeMissingLibraries"), task => task.output = ModLibrary.McLibNetFilesFromInstance(version)) { ProgressWeight = 1d }, new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLibraries"), new List()) { ProgressWeight = 15d } }; // 构造加载器 loaders.Add( new ModLoader.LoaderCombo(Lang.Text("Minecraft.Download.Stage.DownloadLibraries.MainLoader"), loadersLib) { block = false, show = false, ProgressWeight = 16d }); } #endregion #region 下载资源文件 if (ModLibrary.ShouldIgnoreFileCheck(version)) { ModBase.Log("[Download] 已跳过所有 Assets 检查"); } else { var loadersAssets = new List(); // 获取资源文件索引地址 loadersAssets.Add(new ModLoader.LoaderTask>( Lang.Text("Minecraft.Download.Stage.AnalyzeAssetsIndex"), task => { try { var indexFile = DlClientAssetIndexGet(version); var indexFileInfo = new FileInfo(indexFile.LocalPath); if (assetsIndexBehaviour != AssetsIndexExistsBehaviour.AlwaysDownload && indexFile.Check.Check(indexFile.LocalPath) is null) task.output = new List(); else task.output = new List { indexFile }; } catch (Exception ex) { throw new Exception(Lang.Text("Minecraft.Download.Error.AssetIndexAnalysisFailed"), ex); } }) { ProgressWeight = 0.5d, show = false }); // 下载资源文件索引 loadersAssets.Add(new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadAssetsIndex"), new List()) { ProgressWeight = 2d }); // 要求独立更新索引 if (assetsIndexBehaviour == AssetsIndexExistsBehaviour.DownloadInBackground) { var loadersAssetsUpdate = new List(); string tempAddress = null; string realAddress = null; loadersAssetsUpdate.Add(new ModLoader.LoaderTask>( Lang.Text("Minecraft.Download.Stage.AnalyzeAssetsIndex.Background"), task => { var backAssetsFile = DlClientAssetIndexGet(version); realAddress = backAssetsFile.LocalPath; tempAddress = ModBase.pathTemp + @"Cache\" + backAssetsFile.LocalName; backAssetsFile.LocalPath = tempAddress; task.output = new List { backAssetsFile }; // 检查是否需要更新:每天只更新一次 if (File.Exists(realAddress) && Math.Abs((File.GetLastWriteTime(realAddress).Date - DateTime.Now.Date).TotalDays) < 1d) { ModBase.Log("[Download] 无需更新资源文件索引,取消"); task.Abort(); } })); loadersAssetsUpdate.Add(new LoaderDownload( Lang.Text("Minecraft.Download.Stage.DownloadAssetsIndex.Background"), new List())); loadersAssetsUpdate.Add(new ModLoader.LoaderTask, string>( Lang.Text("Minecraft.Download.Stage.CopyAssetsIndex.Background"), task => { ModBase.CopyFile(tempAddress, realAddress); ModLaunch.McLaunchLog("后台更新资源文件索引成功:" + tempAddress); })); var updater = new ModLoader.LoaderCombo( Lang.Text("Minecraft.Download.Stage.UpdateAssetsIndex.Background"), loadersAssetsUpdate); ModBase.Log("[Download] 开始后台检查资源文件索引"); updater.Start(); } // 获取资源文件地址 loadersAssets.Add(new ModLoader.LoaderTask>( Lang.Text("Minecraft.Download.Stage.AnalyzeMissingAssets"), task => { ModLoader.LoaderBase argprogressFeed = task; task.output = ModAssets.McAssetsFixList(version, checkAssetsHash, ref argprogressFeed); task = (ModLoader.LoaderTask>)argprogressFeed; }) { ProgressWeight = 3d }); // 下载资源文件 loadersAssets.Add( new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadAssets"), new List()) { ProgressWeight = 25d }); // 构造加载器 loaders.Add( new ModLoader.LoaderCombo(Lang.Text("Minecraft.Download.Stage.DownloadAssets.MainLoader"), loadersAssets) { block = false, show = false, ProgressWeight = 30.5d }); } #endregion return loaders; } public enum AssetsIndexExistsBehaviour { /// /// 如果文件存在,则不进行下载。 /// DontDownload, /// /// 如果文件存在,则启动新的下载加载器进行独立的更新。 /// DownloadInBackground, /// /// 如果文件存在,也同样进行下载。 /// AlwaysDownload } #endregion #region DlClientList | Minecraft 客户端 版本列表 /// /// 所有正式版的 Minecraft Drop 序数。 /// 若从未完成过获取,返回 Nothing;否则必定存在元素,且从高到低排列。 /// public static List AllDrops { get { lock (_allDropsLock) { if (field is null) { var rawData = States.Game.Drops; if (string.IsNullOrEmpty(rawData)) field = new List(); else field = rawData.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) .Select(d => (int)Math.Round(ModBase.Val(d))).ToList(); } return field.Count != 0 ? field : null; } } set { lock (_allDropsLock) { field = value; States.Game.Drops = value.Join(","); } } } private static readonly object _allDropsLock = new(); // 主加载器 public struct DlClientListResult { /// /// 数据来源名称,如“Mojang”,“BMCLAPI”。 /// public string SourceName; /// /// 是否为官方的实时数据。 /// public bool IsOfficial; /// /// 获取到的 Json 数据。 /// public JsonObject Value; // ''' // ''' 官方源的失败原因。若没有则为 Nothing。 // ''' // Public OfficialError As Exception } /// /// Minecraft 客户端 版本列表,主加载器。 /// 若要求镜像源必须包含某个版本,则将该版本 ID 作为输入(#5195)。 /// public static ModLoader.LoaderTask dlClientListLoader = new("DlClientList Main", DlClientListMain); private static void DlClientListMain(ModLoader.LoaderTask loader) { switch (Config.Download.VersionListSource) { case 0: { DlSourceLoader(loader, new List, int>> { new(dlClientListBmclapiLoader, 30), new(dlClientListMojangLoader, 30 + 60) }, loader.isForceRestarting); break; } case 1: { DlSourceLoader(loader, new List, int>> { new(dlClientListMojangLoader, 5), new(dlClientListBmclapiLoader, 5 + 30) }, loader.isForceRestarting); break; } default: { DlSourceLoader(loader, new List, int>> { new(dlClientListMojangLoader, 60), new(dlClientListBmclapiLoader, 60 + 60) }, loader.isForceRestarting); break; } } // 提取所有 Drop 序数 var drops = new List(); foreach (JsonObject version in loader.output.Value["versions"].AsArray()) drops.Add(McInstanceInfo.VersionToDrop((string)version["id"])); AllDrops = drops.Distinct().OrderByDescending(d => d).ToList(); } // 各个下载源的分加载器 /// /// Minecraft 客户端 版本列表,Mojang 官方源加载器。 /// public static ModLoader.LoaderTask dlClientListMojangLoader = new("DlClientList Mojang", DlClientListMojangMain); private static bool isNewClientVersionHinted = false; // MC 更新提示 private static bool _DlClientListMojangMain_IsHinted; private static void DlClientListMojangMain(ModLoader.LoaderTask loader) { var startTime = TimeUtils.GetTimeTick(); var json = (JsonObject)Requester.FetchJson("https://launchermeta.mojang.com/mc/game/version_manifest.json"); try { var versions = (JsonArray)json["versions"]; if (versions.Count < 200) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Mojang", json)); // 添加 UVMC 项 var cacheFilePath = ModBase.pathTemp + @"Cache\uvmc-download.json"; if (!File.Exists(cacheFilePath)) try { var unlistedJson = (JsonObject)Requester.FetchJson( "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto/version_manifest.json"); File.WriteAllText(cacheFilePath, unlistedJson.ToString()); } catch (Exception ex) { ModBase.Log("[Download] 未列出的版本官方源下载失败: " + ex.Message); } try { var cachedJson = (JsonObject)ModBase.GetJson(ModBase.ReadFile(cacheFilePath)); versions.Merge(cachedJson["versions"]); } catch (Exception ex) { ModBase.Log(ex, "[Download] UVMC 列表加载失败,忽略列表内容"); } // 确定官方源是否可用 if (!dlPreferMojang) { var deltaTime = TimeUtils.GetTimeTick() - startTime; dlPreferMojang = deltaTime < 4000; ModBase.Log($"[Download] Mojang 官方源加载耗时:{deltaTime}ms,{(dlPreferMojang ? "可优先使用官方源" : "不优先使用官方源")}"); } // 添加 PCL 特供项 // 这个社区版下不了 // If File.Exists(PathTemp & "Cache\download.json") Then Versions.Merge(GetJson(ReadFile(PathTemp & "Cache\download.json"))) // 返回 loader.output = new DlClientListResult { IsOfficial = true, SourceName = Lang.Text("Download.Source.MojangOfficial"), Value = json }; string version; // 快照版 version = (string)json["latest"]["snapshot"]; if (Config.Tool.SnapshotNotification && States.Tool.LastSnapshot != "" && States.Tool.LastSnapshot != version && !_DlClientListMojangMain_IsHinted) { _DlClientListMojangMain_IsHinted = true; McDownloadClientUpdateHint(version, json); } States.Tool.LastSnapshot = version ?? "Nothing"; // 正式版 version = (string)json["latest"]["release"]; if (Config.Tool.ReleaseNotification && States.Tool.LastRelease != "" && States.Tool.LastRelease != version && !_DlClientListMojangMain_IsHinted) { _DlClientListMojangMain_IsHinted = true; McDownloadClientUpdateHint(version, json); } States.Tool.LastRelease = version; } catch (Exception ex) { throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Mojang", ""), ex); } } /// /// Minecraft 客户端 版本列表,BMCLAPI 源加载器。 /// public static ModLoader.LoaderTask dlClientListBmclapiLoader = new("DlClientList Bmclapi", DlClientListBmclapiMain); private static void DlClientListBmclapiMain(ModLoader.LoaderTask loader) { var json = (JsonObject)Requester.FetchJson( "https://bmclapi2.bangbang93.com/mc/game/version_manifest.json"); try { var versions = (JsonArray)json["versions"]; if (versions.Count < 200) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "BMCLAPI", json)); // 添加 UVMC 项 var cacheFilePath = ModBase.pathTemp + @"Cache\uvmc-download.json"; if (!File.Exists(cacheFilePath)) try { var unlistedJson = (JsonObject)Requester.FetchJson( "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto/version_manifest.json"); File.WriteAllText(cacheFilePath, unlistedJson.ToString()); } catch (Exception ex) { ModBase.Log("[Download] 未列出的版本镜像源下载失败: " + ex.Message); } try { var cachedJson = (JsonObject)ModBase.GetJson(ModBase.ReadFile(cacheFilePath)); versions.Merge(cachedJson["versions"]); } catch (Exception ex) { ModBase.Log(ex, "[Download] UVMC 列表加载失败,忽略列表内容"); } // 检查是否有要求的版本(#5195) if (!string.IsNullOrEmpty(loader.input)) { var id = loader.input; if (dlClientListLoader.output.Value is not null && !dlClientListLoader.output.Value["versions"].AsArray().Any(v => (string)v["id"] == id)) throw new Exception(Lang.Text("Minecraft.Download.Error.BmclapiMissingTargetVersion", id)); } // 返回 loader.output = new DlClientListResult { IsOfficial = false, SourceName = "BMCLAPI", Value = json }; } catch (Exception ex) { throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "BMCLAPI", json), ex); } } /// /// 获取某个版本的 Json 下载地址,若失败则返回 Nothing。必须在工作线程执行。 /// public static object DlClientListGet(string id) { try { // 确认版本格式标准 id = id.Replace("_", "-"); // 1.7.10_pre4 在版本列表中显示为 1.7.10-pre4 if (id != "1.0" && id.EndsWithF(".0")) id = id.Substring(0, id.Length - 2); // OptiFine 1.8 的下载会触发此问题,显示版本为 1.8.0 // 获取 Minecraft 版本列表 switch (dlClientListLoader.State) { case ModBase.LoadState.Finished: { // 从当前的结果获取目标版本… foreach (JsonObject Version in dlClientListLoader.output.Value["versions"].AsArray()) if ((string)Version["id"] == id) return Version["url"].ToString(); // …如果没有,则重新尝试获取(在版本刚更新时可能出现这种情况,#5195) dlClientListLoader.WaitForExit(id, isForceRestart: true); break; } case ModBase.LoadState.Loading: { dlClientListLoader.WaitForExit(id); break; } case ModBase.LoadState.Failed: case ModBase.LoadState.Aborted: case ModBase.LoadState.Waiting: { dlClientListLoader.WaitForExit(id, isForceRestart: true); break; } } // 重新查找版本 foreach (JsonObject Version in dlClientListLoader.output.Value["versions"].AsArray()) if ((string)Version["id"] == id) return Version["url"].ToString(); ModBase.Log($"未发现版本 {id} 的 json 下载地址,版本列表返回为:{"\r\n"}{dlClientListLoader.output.Value}", ModBase.LogLevel.Debug); return null; } catch (Exception ex) { ModBase.Log(ex, $"获取版本 {id} 的 json 下载地址失败"); return null; } } #endregion #region DlOptiFineList | OptiFine 版本列表 public struct DlOptiFineListResult { /// /// 数据来源名称,如“Official”,“BMCLAPI”。 /// public string sourceName; /// /// 是否为官方的实时数据。 /// public bool isOfficial; /// /// 获取到的数据。 /// public List Value; } public class DlOptiFineListEntry { /// /// 显示名称,已去除 HD_U 字样,如“1.12.2 C8”。 /// public string DisplayName; /// /// 是否为测试版。 /// public bool IsPreview; /// /// 原始文件名称,如“preview_OptiFine_1.11_HD_U_E1_pre.jar”。 /// public string NameFile; /// /// 对应的版本名称,如“1.13.2-OptiFine_HD_U_E6”。 /// public string NameVersion; /// /// 发布时间,格式为“yyyy/mm/dd”。OptiFine 源无此数据。 /// public string ReleaseTime; /// /// 需要的最低 Forge 版本。空字符串为无限制,Nothing 为不兼容,“28.1.56” 表示版本号,“1161” 表示版本号的最后一位。 /// public string RequiredForgeVersion; /// /// 对应的 Minecraft 版本,如“1.12.2”。 /// public string Inherit { get => field; set { if (value.EndsWithF(".0")) value = value.Substring(0, value.Length - 2); field = value; } } } /// /// OptiFine 版本列表,主加载器。 /// public static ModLoader.LoaderTask dlOptiFineListLoader = new("DlOptiFineList Main", DlOptiFineListMain); private static void DlOptiFineListMain(ModLoader.LoaderTask loader) { switch (Config.Download.VersionListSource) { case 0: { DlSourceLoader(loader, new List, int>> { new(dlOptiFineListBmclapiLoader, 30), new(dlOptiFineListOfficialLoader, 30 + 60) }, loader.isForceRestarting); break; } case 1: { DlSourceLoader(loader, new List, int>> { new(dlOptiFineListOfficialLoader, 5), new(dlOptiFineListBmclapiLoader, 5 + 30) }, loader.isForceRestarting); break; } default: { DlSourceLoader(loader, new List, int>> { new(dlOptiFineListOfficialLoader, 60), new(dlOptiFineListBmclapiLoader, 60 + 60) }, loader.isForceRestarting); break; } } } /// /// OptiFine 版本列表,官方源。 /// public static ModLoader.LoaderTask dlOptiFineListOfficialLoader = new("DlOptiFineList Official", DlOptiFineListOfficialMain); private static void DlOptiFineListOfficialMain(ModLoader.LoaderTask loader) { string result = ""; using var resp = HttpRequest .Create("https://optifine.net/downloads") .WithHeader("Accept", "application/json, text/javascript, */*; q=0.01") .WithHeader("Accept-Language", "en-US,en;q=0.5") .WithHeader("X-Requested-With", "XMLHttpRequest") .SendAsync() .GetAwaiter() .GetResult(); resp.EnsureSuccessStatusCode(); result = resp.AsString(); if (result.Length < 200) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "OptiFine", result)); try { var forge = result.RegexSearch("(?<=colForge'>)[^<]*"); var releaseTime = result.RegexSearch("(?<=colDate'>)[^<]+"); var name = result.RegexSearch("(?<=OptiFine_)[0-9A-Za-z_.]+(?=.jar\")"); if (releaseTime.Count != name.Count) throw new Exception(Lang.Text("Minecraft.Download.Error.OptiFineTimeDataMismatch")); if (forge.Count != name.Count) throw new Exception(Lang.Text("Minecraft.Download.Error.OptiFineForgeCompatMismatch")); if (releaseTime.Count < 10) throw new Exception( Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "OptiFine", result)); // 转化为列表输出 var versions = new List(); for (int i = 0, loopTo = releaseTime.Count - 1; i <= loopTo; i++) { name[i] = name[i].Replace("_", " "); var releaseDate = DateTime.ParseExact(releaseTime[i], ["d.M.yyyy", "dd.M.yyyy", "d.MM.yyyy", "dd.MM.yyyy"], CultureInfo.InvariantCulture, DateTimeStyles.None); var entry = new DlOptiFineListEntry { DisplayName = name[i].Replace("HD U ", "").Replace(".0 ", " "), ReleaseTime = Lang.Date(releaseDate, "d"), IsPreview = name[i].ContainsF("pre", true), Inherit = name[i].Split(" ")[0], NameFile = (name[i].ContainsF("pre", true) ? "preview_" : "") + "OptiFine_" + name[i].Replace(" ", "_") + ".jar", RequiredForgeVersion = forge[i].Replace("Forge ", "").Replace("#", "") }; if (entry.RequiredForgeVersion.Contains("N/A")) entry.RequiredForgeVersion = null; entry.NameVersion = entry.Inherit + "-OptiFine_" + name[i].Replace(" ", "_").Replace(entry.Inherit + "_", ""); versions.Add(entry); } loader.output = new DlOptiFineListResult { isOfficial = true, sourceName = Lang.Text("Download.Source.OptiFineOfficial"), Value = versions }; } catch (Exception ex) { throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "OptiFine", result), ex); } } /// /// OptiFine 版本列表,BMCLAPI。 /// public static ModLoader.LoaderTask dlOptiFineListBmclapiLoader = new("DlOptiFineList Bmclapi", DlOptiFineListBmclapiMain); private static void DlOptiFineListBmclapiMain(ModLoader.LoaderTask loader) { var json = (JsonArray)Requester.FetchJson("https://bmclapi2.bangbang93.com/optifine/versionList"); try { var versions = new List(); foreach (JsonObject Token in json) { var entry = new DlOptiFineListEntry { DisplayName = (Token["mcversion"] + Token["type"].ToString().Replace("HD_U", "").Replace("_", " ") + " " + Token["patch"]).Replace(".0 ", " "), ReleaseTime = "", IsPreview = Token["patch"].ToString().ContainsF("pre", true), Inherit = Token["mcversion"].ToString(), NameFile = Token["filename"].ToString(), RequiredForgeVersion = (Token["forge"] ?? "").ToString().Replace("Forge ", "").Replace("#", "") }; if (entry.RequiredForgeVersion.Contains("N/A")) entry.RequiredForgeVersion = null; entry.NameVersion = entry.Inherit + "-OptiFine_" + (Token["type"] + " " + Token["patch"]) .Replace(".0 ", " ").Replace(" ", "_").Replace(entry.Inherit + "_", ""); versions.Add(entry); } loader.output = new DlOptiFineListResult { isOfficial = false, sourceName = "BMCLAPI", Value = versions }; } catch (Exception ex) { throw new Exception( Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "OptiFine BMCLAPI", json), ex); } } #endregion #region DlForgeList | Forge Minecraft 版本列表 public struct DlForgeListResult { /// /// 数据来源名称,如“Official”,“BMCLAPI”。 /// public string sourceName; /// /// 是否为官方的实时数据。 /// public bool isOfficial; /// /// 获取到的数据。 /// public List Value; } /// /// Forge 版本列表,主加载器。 /// public static ModLoader.LoaderTask dlForgeListLoader = new("DlForgeList Main", DlForgeListMain); private static void DlForgeListMain(ModLoader.LoaderTask loader) { switch (Config.Download.VersionListSource) { case 0: { DlSourceLoader(loader, new List, int>> { new(dlForgeListBmclapiLoader, 30), new(dlForgeListOfficialLoader, 30 + 60) }, loader.isForceRestarting); break; } case 1: { DlSourceLoader(loader, new List, int>> { new(dlForgeListOfficialLoader, 5), new(dlForgeListBmclapiLoader, 5 + 30) }, loader.isForceRestarting); break; } default: { DlSourceLoader(loader, new List, int>> { new(dlForgeListOfficialLoader, 60), new(dlForgeListBmclapiLoader, 60 + 60) }, loader.isForceRestarting); break; } } } /// /// Forge 版本列表,官方源。 /// public static ModLoader.LoaderTask dlForgeListOfficialLoader = new("DlForgeList Official", DlForgeListOfficialMain); private static void DlForgeListOfficialMain(ModLoader.LoaderTask loader) { var result = Requester.FetchString( "https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", new RequestParam { Encoding = Encoding.Default, UseBrowserUserAgent = true }); if (result.Length < 200) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Forge", result)); // 获取所有版本信息 var names = result.RegexSearch("(?<=a href=\"index_)[0-9.]+(_pre[0-9]?)?(?=.html)"); names.Add("1.2.4"); // 1.2.4 不会被匹配上 if (names.Count < 10) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Forge", result)); loader.output = new DlForgeListResult { isOfficial = true, sourceName = Lang.Text("Download.Source.ForgeOfficial"), Value = names }; } /// /// Forge 版本列表,BMCLAPI。 /// public static ModLoader.LoaderTask dlForgeListBmclapiLoader = new("DlForgeList Bmclapi", DlForgeListBmclapiMain); private static void DlForgeListBmclapiMain(ModLoader.LoaderTask loader) { var result = Requester.FetchJson("https://bmclapi2.bangbang93.com/forge/minecraft", new RequestParam { Encoding = Encoding.Default, })?.ToString() ?? ""; if (result.Length < 200) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Forge BMCLAPI", result)); // 获取所有版本信息 var names = result.RegexSearch("[0-9.]+(_pre[0-9]?)?"); if (names.Count < 10) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Forge BMCLAPI", result)); loader.output = new DlForgeListResult { isOfficial = false, sourceName = "BMCLAPI", Value = names }; } #endregion #region DlForgeVersion | Forge 版本列表 public abstract class DlForgelikeEntry : IComparable { public enum ForgelikeType { Forge, NeoForge, Cleanroom } /// /// Forgelike 种类。Forge、NeoForge、Cleanroom。 /// public ForgelikeType forgeType; /// /// 对应的 Minecraft 版本,如“1.12.2”。 /// public string Inherit; /// /// 标准化后的版本号,仅可用于比较与排序。 /// 格式:Major.Minor.Build.Revision /// Forge:如 “50.1.9.0”(最后一位固定为 0)、“14.22.1.2478”(Legacy)。 /// NeoForge:如 “20.4.30.0”(最后一位固定为 0)、“19.47.1.99”(Legacy:第一位固定为 19)。 /// Cleanroom:如 “0.2.4.1”(Alpha:最后一位固定为 1)。 /// public Version version; /// /// 可对玩家显示的非格式化版本名。 /// Forge:如 “50.1.9”、“14.22.1.2478”(Legacy)。 /// NeoForge:如 “20.4.30-beta”、“47.1.99”(Legacy)。 /// Cleanroom:如 “0.2.4-alpha”。 /// public string VersionName; /// /// 加载器名称。Forge / NeoForge / Cleanroom。 /// public string LoaderName => forgeType.ToString(); /// /// 文件扩展名。不以小数点开头。 /// public string FileExtension { get { if (forgeType == 0) return ((DlForgeVersionEntry)this).Category == "installer" ? "jar" : "zip"; return "jar"; } } /// /// Forge:MC 版本是否小于 1.13。 /// NeoForge:MC 版本是否为 1.20.1。 /// Cleanroom:固定为 False。 /// public bool IsLegacy { get { // Cleanroom 始终为 False if ((int)forgeType == 2) return false; // 虽然很抽象,但确实可以这样判断 // Forge:1.13+ 的版本号首位都大于 20 // NeoForge:1.20.1 的版本号首位人为规定为 19 开头 return version.Major < 20; } } public int CompareTo(DlForgelikeEntry other) { if (version != other.version) return version.CompareTo(other.version); return McVersionComparer.CompareVersion(VersionName, other.VersionName); } } public class DlForgeVersionEntry : DlForgelikeEntry { /// /// 安装类型。有 installer、client、universal 三种。 /// public string Category; /// /// 用于下载的文件版本名。可能在 Version 的基础上添加了分支。 /// public string FileVersion; /// /// 文件的 MD5 或 SHA1(BMCLAPI 的老版本是 MD5,新版本是 SHA1;官方源总是 MD5)。 /// public string Hash; /// /// 是否为推荐版本。 /// public bool IsRecommended; /// /// 发布时间,格式为“yyyy/MM/dd HH:mm”。 /// public string ReleaseTime; public DlForgeVersionEntry(string version, string branch, string inherit) { // 司马版本的特殊处理 if (version == "11.15.1.2318" || version == "11.15.1.1902" || version == "11.15.1.1890") branch = "1.8.9"; if (branch is null && inherit == "1.7.10" && double.Parse(version.Split(".")[3]) >= 1300d) branch = "1.7.10"; // 为 DlForgelikeEntry 提供所有信息 forgeType = ForgelikeType.Forge; VersionName = version; this.version = new Version(version); this.Inherit = inherit; FileVersion = version + (branch is null ? "" : "-" + branch); } } /// /// Forge 版本列表,主加载器。 /// public static void DlForgeVersionMain(ModLoader.LoaderTask> loader) { var dlForgeVersionOfficialLoader = new ModLoader.LoaderTask>("DlForgeVersion Official", DlForgeVersionOfficialMain); var dlForgeVersionBmclapiLoader = new ModLoader.LoaderTask>("DlForgeVersion Bmclapi", DlForgeVersionBmclapiMain); switch (Config.Download.VersionListSource) { case 0: { DlSourceLoader(loader, new List>, int>> { new(dlForgeVersionBmclapiLoader, 30), new(dlForgeVersionOfficialLoader, 30 + 60) }, loader.isForceRestarting); break; } case 1: { DlSourceLoader(loader, new List>, int>> { new(dlForgeVersionOfficialLoader, 5), new(dlForgeVersionBmclapiLoader, 5 + 30) }, loader.isForceRestarting); break; } default: { DlSourceLoader(loader, new List>, int>> { new(dlForgeVersionOfficialLoader, 60), new(dlForgeVersionBmclapiLoader, 60 + 60) }, loader.isForceRestarting); break; } } } /// /// Forge 版本列表,官方源。 /// public static void DlForgeVersionOfficialMain(ModLoader.LoaderTask> loader) { string result; try { result = Requester.FetchString( "https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" + loader.input.Replace("-", "_") + ".html", new RequestParam { UseBrowserUserAgent = true }); // 兼容 Forge 1.7.10-pre4,#4057 } catch (WebException) { throw; } catch (Exception ex) { if (ex.Message.Contains("(404)")) throw new Exception(Lang.Text("Minecraft.Download.Error.NotFound")); throw; } if (result.Length < 1000) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Forge", result)); var versions = new List(); try { // 分割版本信息 var versionCodes = result.Substring(0, result.LastIndexOfF("")) .Split(" )[^<]+"); category = "installer"; } else if (versionCode.Contains("classifier-universal\"")) { // 类型为 universal.zip,支持范围 751~449 (1.6.1 部分), 682~183 (1.5.1 ~ 1.3.2 部分) versionCode = versionCode.Substring(versionCode.IndexOfF("universal.zip")); mD5 = versionCode.RegexSeek("(?<=MD5: )[^<]+"); category = "universal"; } else if (versionCode.Contains("client.zip")) { // 类型为 client.zip,支持范围 182~ (1.3.2 部分 ~) versionCode = versionCode.Substring(versionCode.IndexOfF("client.zip")); mD5 = versionCode.RegexSeek("(?<=MD5: )[^<]+"); category = "client"; } else { // 没有任何下载(1.6.4 有一部分这种情况) continue; } // 添加进列表 versions.Add(new DlForgeVersionEntry(name, branch, inherit) { Category = category, IsRecommended = isRecommended, Hash = mD5.Trim('\r', '\n'), ReleaseTime = releaseTime }); } catch (Exception ex) { throw new Exception( Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Forge", versionCode), ex); } } } catch (Exception ex) { throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Forge", result), ex); } if (!versions.Any()) throw new Exception(Lang.Text("Minecraft.Download.Error.NotFound")); loader.output = versions; } /// /// Forge 版本列表,BMCLAPI。 /// public static void DlForgeVersionBmclapiMain(ModLoader.LoaderTask> loader) { var json = (JsonArray)Requester.FetchJson( "https://bmclapi2.bangbang93.com/forge/minecraft/" + loader.input.Replace("-", "_")); // 兼容 Forge 1.7.10-pre4,#4057 var versions = new List(); try { var recommended = ModDownloadLib.McDownloadForgeRecommendedGet(loader.input); foreach (JsonObject Token in json) { // 分类与 Hash 获取 string hash = null; var category = "unknown"; var proi = -1; foreach (JsonObject File in Token["files"].AsArray()) switch (File["category"].ToString() ?? "") { case "installer": { if (File["format"].ToString() == "jar") { // 类型为 installer.jar,支持范围 ~753 (~ 1.6.1 部分), 738~684 (1.5.2 全部) hash = (string)File["hash"]; category = "installer"; proi = 2; } break; } case "universal": { if (proi <= 1 && File["format"].ToString() == "zip") { // 类型为 universal.zip,支持范围 751~449 (1.6.1 部分), 682~183 (1.5.1 ~ 1.3.2 部分) hash = (string)File["hash"]; category = "universal"; proi = 1; } break; } case "client": { if (proi <= 0 && File["format"].ToString() == "zip") { // 类型为 client.zip,支持范围 182~ (1.3.2 部分 ~) hash = (string)File["hash"]; category = "client"; proi = 0; } break; } } // 获取 Entry var branch = (string)Token["branch"]; var name = (string)Token["version"]; // 基础信息获取 var entry = new DlForgeVersionEntry(name, branch, loader.input) { Hash = hash, Category = category, IsRecommended = (recommended ?? "") == (name ?? "") }; var timeSplit = Token["modified"].ToString().Split('-', 'T', ':', '.', ' ', '/'); entry.ReleaseTime = Lang.Date(Token["modified"].ToObject().ToLocalTime(), "g"); // 添加项 versions.Add(entry); } } catch (Exception ex) { throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Forge BMCLAPI", json), ex); } if (!versions.Any()) throw new Exception(Lang.Text("Minecraft.Download.Error.NotFound")); loader.output = versions; } #endregion #region DlNeoForgeList | NeoForge 版本列表 public struct DlNeoForgeListResult { /// /// 数据来源名称,如“Official”,“BMCLAPI”。 /// public string sourceName; /// /// 是否为官方的实时数据。 /// public bool isOfficial; /// /// 所有版本的列表。已经按从新到老排序。 /// public List Value; } public class DlNeoForgeListEntry : DlForgelikeEntry { /// /// API 使用的原始版本字符串,如 “20.4.30-beta”、“1.20.1-47.1.99”(Legacy)。 /// public string ApiName; /// /// 是否是 Beta 版。 /// public bool IsBeta; public DlNeoForgeListEntry(string apiName) { forgeType = ForgelikeType.NeoForge; this.ApiName = apiName; IsBeta = apiName.Contains("beta") || apiName.Contains("alpha"); if (apiName.Contains("1.20.1")) // 1.20.1-47.1.99 { VersionName = apiName.Replace("1.20.1-", ""); version = new Version("19." + VersionName); Inherit = "1.20.1"; } else if (apiName.StartsWith("0.")) // 0.25w14craftmine.3-beta { VersionName = apiName; var segments = apiName.BeforeFirst("-").Split('.'); version = new Version(0, 0, int.Parse(segments.Last())); Inherit = segments[1]; } else // 20.4.30-beta;26.1.0.0-alpha.1+snapshot-1 { VersionName = apiName; version = new Version(apiName.BeforeFirst("-")); if (version.Major >= 24) Inherit = $"{version.Major}.{version.Minor}{(version.Build > 0 ? $".{version.Build}" : "")}"; else Inherit = "1." + version.Major + (version.Minor > 0 ? "." + version.Minor : ""); if (VersionName.Contains("+")) Inherit += "-" + VersionName.AfterFirst("+"); } } /// /// 文件在官网的基础地址,不包含后缀。 /// public string UrlBase { get { var packageName = IsLegacy ? "forge" : "neoforge"; return $"https://maven.neoforged.net/releases/net/neoforged/{packageName}/{ApiName}/{packageName}-{ApiName}"; } } } /// /// NeoForge 版本列表,主加载器。 /// public static ModLoader.LoaderTask dlNeoForgeListLoader = new("DlNeoForgeList Main", DlNeoForgeListMain); private static void DlNeoForgeListMain(ModLoader.LoaderTask loader) { switch (Config.Download.VersionListSource) { case 0: { DlSourceLoader(loader, new List, int>> { new(dlNeoForgeListBmclapiLoader, 30), new(dlNeoForgeListOfficialLoader, 30 + 60) }, loader.isForceRestarting); break; } case 1: { DlSourceLoader(loader, new List, int>> { new(dlNeoForgeListOfficialLoader, 5), new(dlNeoForgeListBmclapiLoader, 5 + 30) }, loader.isForceRestarting); break; } default: { DlSourceLoader(loader, new List, int>> { new(dlNeoForgeListOfficialLoader, 60), new(dlNeoForgeListBmclapiLoader, 60 + 60) }, loader.isForceRestarting); break; } } } /// /// NeoForge 版本列表,官方源。 /// public static ModLoader.LoaderTask dlNeoForgeListOfficialLoader = new("DlNeoForgeList Official", DlNeoForgeListOfficialMain); private static void DlNeoForgeListOfficialMain(ModLoader.LoaderTask loader) { // 获取版本列表 JSON var resultLatest = Requester.FetchJson( "https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge", new RequestParam { UseBrowserUserAgent = true }).ToString(); var resultLegacy = Requester.FetchJson( "https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge", new RequestParam { UseBrowserUserAgent = true }).ToString(); if (resultLatest.Length < 100 || resultLegacy.Length < 100) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "NeoForge", resultLatest + "\r\n\r\n" + resultLegacy)); // 解析 try { loader.output = new DlNeoForgeListResult { isOfficial = true, sourceName = Lang.Text("Download.Source.NeoForgeOfficial"), Value = GetNeoForgeEntries(resultLatest, resultLegacy) }; } catch (Exception ex) { throw new Exception( Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "NeoForge", resultLatest + "\r\n\r\n" + resultLegacy), ex); } } /// /// NeoForge 版本列表,BMCLAPI。 /// public static ModLoader.LoaderTask dlNeoForgeListBmclapiLoader = new("DlNeoForgeList Bmclapi", DlNeoForgeListBmclapiMain); public static void DlNeoForgeListBmclapiMain(ModLoader.LoaderTask loader) { // 获取版本列表 JSON var resultLatest = Requester.FetchJson( "https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge", new RequestParam { UseBrowserUserAgent = true }).ToString(); var resultLegacy = Requester.FetchJson( "https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge", new RequestParam { UseBrowserUserAgent = true }).ToString(); if (resultLatest.Length < 100 || resultLegacy.Length < 100) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "NeoForge BMCLAPI", resultLatest + "\r\n\r\n" + resultLegacy)); // 解析 try { loader.output = new DlNeoForgeListResult { isOfficial = true, sourceName = "BMCLAPI", Value = GetNeoForgeEntries(resultLatest, resultLegacy) }; } catch (Exception ex) { throw new Exception( Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "NeoForge BMCLAPI", resultLatest + "\r\n\r\n" + resultLegacy), ex); } } private static List GetNeoForgeEntries(string latestJson, string latestLegacyJson) { var versionNames = ModBase.RegexSearch(latestLegacyJson + latestJson, RegexPatterns.DlNeoForgeVersion); var versions = versionNames.Where(name => name != "47.1.82").Select(name => new DlNeoForgeListEntry(name)) .OrderByDescending(a => a).ToList(); // 这个版本虽然在版本列表中,但不能下载 if (!versions.Any()) throw new Exception(Lang.Text("Minecraft.Download.Error.NotFound")); return versions; } #endregion #region DlCleanroomList | Cleanroom 版本列表 public struct DlCleanroomListResult { /// /// 数据来源名称,如“Official”,“BMCLAPI”。 /// public string sourceName; /// /// 是否为官方的实时数据。 /// public bool isOfficial; /// /// 所有版本的列表。已经按从新到老排序。 /// public List Value; } public class DlCleanroomListEntry : DlForgelikeEntry { /// /// API 使用的原始版本字符串,如 “0.2.4-alpha”。 /// public string ApiName; /// /// 是否是 Beta 版。 /// public bool IsBeta; public DlCleanroomListEntry(string apiName) { forgeType = ForgelikeType.Cleanroom; this.ApiName = apiName; IsBeta = apiName.Contains("alpha"); VersionName = apiName; version = new Version(apiName.BeforeFirst("-")); Inherit = "1.12.2"; } /// /// 文件在官网的基础地址,不包含后缀。 /// public string UrlBase => $"https://github.com/CleanroomMC/Cleanroom/releases/download/{ApiName}/cleanroom-{ApiName}"; } /// /// Cleanroom 版本列表,主加载器。 /// public static ModLoader.LoaderTask dlCleanroomListLoader = new("DlCleanroomList Main", DlCleanroomListMain); private static void DlCleanroomListMain(ModLoader.LoaderTask loader) { switch (Config.Download.VersionListSource) { case 0: { DlSourceLoader(loader, new List, int>> { new(dlCleanroomListOfficialLoader, 30) }, loader.isForceRestarting); break; } case 1: { DlSourceLoader(loader, new List, int>> { new(dlCleanroomListOfficialLoader, 5) }, loader.isForceRestarting); break; } default: { DlSourceLoader(loader, new List, int>> { new(dlCleanroomListOfficialLoader, 60) }, loader.isForceRestarting); break; } } } /// /// Cleanroom 版本列表,官方源。 /// public static ModLoader.LoaderTask dlCleanroomListOfficialLoader = new("DlCleanroomList Official", DlCleanroomListOfficialMain); private static void DlCleanroomListOfficialMain(ModLoader.LoaderTask loader) { // 获取版本列表 JSON var resultLatest = Requester.FetchJson( "https://api.github.com/repos/CleanroomMC/Cleanroom/releases", new RequestParam { UseBrowserUserAgent = true }).ToString(); if (resultLatest.Length < 100) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Cleanroom", resultLatest)); // 解析 try { loader.output = new DlCleanroomListResult { isOfficial = true, sourceName = Lang.Text("Download.Source.CleanroomOfficial"), Value = GetCleanroomEntries(resultLatest) }; } catch (Exception ex) { throw new Exception( Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Cleanroom", resultLatest), ex); } } private static List GetCleanroomEntries(string latestJson) { var versions = new List(); var json = JsonArray.Parse(latestJson); foreach (JsonObject Token in json.AsArray()) versions.Add(new DlCleanroomListEntry(Token["tag_name"].ToString()) { forgeType = (DlForgelikeEntry.ForgelikeType)2 }); if (!versions.Any()) throw new Exception(Lang.Text("Minecraft.Download.Error.NoAvailableVersion")); versions = versions.OrderByDescending(a => a.version).ToList(); return versions; } #endregion #region DlLiteLoaderList | LiteLoader 版本列表 public struct DlLiteLoaderListResult { /// /// 数据来源名称,如“Official”,“BMCLAPI”。 /// public string sourceName; /// /// 是否为官方的实时数据。 /// public bool isOfficial; /// /// 获取到的数据。 /// public List Value; /// /// 官方源的失败原因。若没有则为 Nothing。 /// public Exception officialError; } public class DlLiteLoaderListEntry { /// /// 实际的文件名,如“liteloader-installer-1.12-00-SNAPSHOT.jar”。 /// public string FileName; /// /// 对应的 Minecraft 版本,如“1.12.2”。 /// public string Inherit; /// /// 是否为 1.7 及更早的远古版。 /// public bool IsLegacy; /// /// 是否为测试版。 /// public bool IsPreview; /// /// 对应的 Json 项。 /// public JsonNode jsonToken; /// /// 文件的 MD5。 /// public string MD5; /// /// 发布时间,格式为“yyyy/mm/dd HH:mm”。 /// public string ReleaseTime; } /// /// LiteLoader 版本列表,主加载器。 /// public static ModLoader.LoaderTask dlLiteLoaderListLoader = new("DlLiteLoaderList Main", DlLiteLoaderListMain); private static void DlLiteLoaderListMain(ModLoader.LoaderTask loader) { switch (Config.Download.VersionListSource) { case 0: { DlSourceLoader(loader, new List, int>> { new(dlLiteLoaderListBmclapiLoader, 30), new(dlLiteLoaderListOfficialLoader, 30 + 60) }, loader.isForceRestarting); break; } case 1: { DlSourceLoader(loader, new List, int>> { new(dlLiteLoaderListOfficialLoader, 5), new(dlLiteLoaderListBmclapiLoader, 5 + 30) }, loader.isForceRestarting); break; } default: { DlSourceLoader(loader, new List, int>> { new(dlLiteLoaderListOfficialLoader, 60), new(dlLiteLoaderListBmclapiLoader, 60 + 60) }, loader.isForceRestarting); break; } } } /// /// LiteLoader 版本列表,官方源。 /// public static ModLoader.LoaderTask dlLiteLoaderListOfficialLoader = new("DlLiteLoaderList Official", DlLiteLoaderListOfficialMain); private static void DlLiteLoaderListOfficialMain(ModLoader.LoaderTask loader) { var result = (JsonObject)Requester.FetchJson("https://dl.liteloader.com/versions/versions.json"); try { var json = (JsonObject)result["versions"]; var versions = new List(); foreach (var Pair in json) { if (Pair.Key.StartsWithF("1.6") || Pair.Key.StartsWithF("1.5")) continue; var realEntry = (Pair.Value["artefacts"] ?? Pair.Value["snapshots"])["com.mumfrey:liteloader"]["latest"]; versions.Add(new DlLiteLoaderListEntry { Inherit = Pair.Key, IsLegacy = double.Parse(Pair.Key.Split(".")[1]) < 8d, IsPreview = realEntry["stream"].ToString().ToLower() == "snapshot", FileName = "liteloader-installer-" + Pair.Key + (Pair.Key == "1.8" || Pair.Key == "1.9" ? ".0" : "") + "-00-SNAPSHOT.jar", MD5 = (string)realEntry["md5"], ReleaseTime = TimeUtils.FormatUnixTimestamp(long.Parse(realEntry["timestamp"].ToString())), jsonToken = realEntry }); } loader.output = new DlLiteLoaderListResult { isOfficial = true, sourceName = Lang.Text("Download.Source.LiteLoaderOfficial"), Value = versions }; } catch (Exception ex) { throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "LiteLoader", result), ex); } } /// /// LiteLoader 版本列表,BMCLAPI。 /// public static ModLoader.LoaderTask dlLiteLoaderListBmclapiLoader = new("DlLiteLoaderList Bmclapi", DlLiteLoaderListBmclapiMain); private static void DlLiteLoaderListBmclapiMain(ModLoader.LoaderTask loader) { var result = (JsonObject)Requester.FetchJson( "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json"); try { var json = (JsonObject)result["versions"]; var versions = new List(); foreach (var Pair in json) { if (Pair.Key.StartsWithF("1.6") || Pair.Key.StartsWithF("1.5")) continue; var realEntry = (Pair.Value["artefacts"] ?? Pair.Value["snapshots"])["com.mumfrey:liteloader"]["latest"]; versions.Add(new DlLiteLoaderListEntry { Inherit = Pair.Key, IsLegacy = double.Parse(Pair.Key.Split(".")[1]) < 8d, IsPreview = realEntry["stream"].ToString().ToLower() == "snapshot", FileName = "liteloader-installer-" + Pair.Key + (Pair.Key == "1.8" || Pair.Key == "1.9" ? ".0" : "") + "-00-SNAPSHOT.jar", MD5 = (string)realEntry["md5"], ReleaseTime = TimeUtils.FormatUnixTimestamp(long.Parse((string)realEntry["timestamp"])), jsonToken = realEntry }); } loader.output = new DlLiteLoaderListResult { isOfficial = false, sourceName = "BMCLAPI", Value = versions }; } catch (Exception ex) { throw new Exception( Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "LiteLoader BMCLAPI", result), ex); } } #endregion #region DlFabricList | Fabric 列表 public struct DlFabricListResult { /// /// 数据来源名称,如“Official”,“BMCLAPI”。 /// public string sourceName; /// /// 是否为官方的实时数据。 /// public bool isOfficial; /// /// 获取到的数据。 /// public JsonObject Value; } /// /// Fabric 列表,主加载器。 /// public static ModLoader.LoaderTask dlFabricListLoader = new("DlFabricList Main", DlFabricListMain); private static void DlFabricListMain(ModLoader.LoaderTask loader) { switch (Config.Download.VersionListSource) { case 0: { DlSourceLoader(loader, new List, int>> { new(dlFabricListBmclapiLoader, 30), new(dlFabricListOfficialLoader, 30 + 60) }, loader.isForceRestarting); break; } case 1: { DlSourceLoader(loader, new List, int>> { new(dlFabricListOfficialLoader, 5), new(dlFabricListBmclapiLoader, 5 + 30) }, loader.isForceRestarting); break; } default: { DlSourceLoader(loader, new List, int>> { new(dlFabricListOfficialLoader, 60), new(dlFabricListBmclapiLoader, 60 + 60) }, loader.isForceRestarting); break; } } } /// /// Fabric 列表,官方源。 /// public static ModLoader.LoaderTask dlFabricListOfficialLoader = new("DlFabricList Official", DlFabricListOfficialMain); private static void DlFabricListOfficialMain(ModLoader.LoaderTask loader) { var result = (JsonObject)Requester.FetchJson("https://meta.fabricmc.net/v2/versions"); try { var output = new DlFabricListResult { isOfficial = true, sourceName = Lang.Text("Download.Source.FabricOfficial"), Value = result }; if (output.Value["game"] is null || output.Value["loader"] is null || output.Value["installer"] is null) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Fabric", result)); loader.output = output; } catch (Exception ex) { throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Fabric", result), ex); } } /// /// Fabric 列表,BMCLAPI。 /// public static ModLoader.LoaderTask dlFabricListBmclapiLoader = new("DlFabricList Bmclapi", DlFabricListBmclapiMain); private static void DlFabricListBmclapiMain(ModLoader.LoaderTask loader) { var result = (JsonObject)Requester.FetchJson("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions"); try { var output = new DlFabricListResult { isOfficial = false, sourceName = "BMCLAPI", Value = result }; if (output.Value["game"] is null || output.Value["loader"] is null || output.Value["installer"] is null) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Fabric BMCLAPI", result)); loader.output = output; } catch (Exception ex) { throw new Exception( Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Fabric BMCLAPI", result), ex); } } /// /// Fabric API 列表,官方源。 /// public static ModLoader.LoaderTask> dlFabricApiLoader = new("Fabric API List Loader", task => task.output = ModComp.CompFilesGet("fabric-api", false)); /// /// OptiFabric 列表,官方源。 /// public static ModLoader.LoaderTask> dlOptiFabricLoader = new("OptiFabric List Loader", task => task.output = ModComp.CompFilesGet("322385", true)); #endregion #region DlLabyModList | LabyMod 列表 public struct DlLabyModListResult { /// /// 获取到的数据。 /// public JsonObject Value; } /// /// LabyMod 列表,主加载器。 /// public static ModLoader.LoaderTask dlLabyModListLoader = new("DlLabyModList Main", DlLabyModListMain); private static void DlLabyModListMain(ModLoader.LoaderTask loader) { switch (Config.Download.VersionListSource) { case 0: { DlSourceLoader(loader, new List, int>> { new(dlLabyModListOfficialLoader, 30), new(dlLabyModListOfficialLoader, 60) }, loader.isForceRestarting); break; } case 1: { DlSourceLoader(loader, new List, int>> { new(dlLabyModListOfficialLoader, 5), new(dlLabyModListOfficialLoader, 35) }, loader.isForceRestarting); break; } default: { DlSourceLoader(loader, new List, int>> { new(dlLabyModListOfficialLoader, 60), new(dlLabyModListOfficialLoader, 60) }, loader.isForceRestarting); break; } } } /// /// LabyMod 列表,官方源。 /// public static ModLoader.LoaderTask dlLabyModListOfficialLoader = new("DlLabyModList Official", DlLabyModListOfficialMain); private static void DlLabyModListOfficialMain(ModLoader.LoaderTask loader) { JsonObject resultProduction; using (var productionResponse = HttpRequest .Create("https://releases.r2.labymod.net/api/v1/manifest/production/latest.json") .WithHttpVersionOption(HttpVersion.Version20) .SendAsync() .GetAwaiter() .GetResult()) { resultProduction = (JsonObject)ModBase.GetJson(productionResponse.AsString()); } JsonObject resultSnapshot; using (var snapshotResponse = HttpRequest .Create("https://releases.r2.labymod.net/api/v1/manifest/snapshot/latest.json") .WithHttpVersionOption(HttpVersion.Version20) .SendAsync() .GetAwaiter() .GetResult()) { snapshotResponse.EnsureSuccessStatusCode(); resultSnapshot = (JsonObject)ModBase.GetJson(snapshotResponse.AsString()); } var result = new JsonObject(); result.Add("production", resultProduction); result.Add("snapshot", resultSnapshot); try { var output = new DlLabyModListResult { Value = result }; if (output.Value["production"]["labyModVersion"] is null || output.Value["snapshot"]["labyModVersion"] is null) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "LabyMod", result)); loader.output = output; } catch (Exception ex) { throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "LabyMod", result), ex); } } #endregion #region DlMod | Mod 镜像源请求 /// /// 对可能涉及 Mod 镜像源的请求进行处理,返回字符串。 /// 调用 NetGetCodeByRequest,会进行重试。 /// public static string DlModRequest(string url) => DlModRequest(url); /// /// 对可能涉及 Mod 镜像源的请求进行处理,返回字符串或 JSON 对象。 /// 调用 NetGetCodeByRequest,会进行重试。 /// public static T DlModRequest(string url) { var urls = new List>(); var mcimUrl = DlSourceModGet(url); if ((mcimUrl ?? "") != (url ?? "")) switch (Config.Download.Comp.CompSourceSolution) { case 0: { urls.Add(new KeyValuePair(mcimUrl, 5)); urls.Add(new KeyValuePair(mcimUrl, 10)); urls.Add(new KeyValuePair(url, 15)); break; } case 1: { urls.Add(new KeyValuePair(url, 5)); urls.Add(new KeyValuePair(mcimUrl, 5)); urls.Add(new KeyValuePair(url, 15)); urls.Add(new KeyValuePair(mcimUrl, 10)); break; } default: { urls.Add(new KeyValuePair(url, 5)); urls.Add(new KeyValuePair(url, 15)); urls.Add(new KeyValuePair(mcimUrl, 10)); break; } } var exs = ""; foreach (var Source in urls) try { var json = Requester.FetchString(Source.Key, new RequestParam { Timeout = Source.Value * 1000, UseBrowserUserAgent = true }); if (typeof(T) == typeof(string)) return (T)(object)json; return (T)(object)ModBase.GetJson(json); } catch (Exception ex) { // 镜像源可能随机爆炸,忽略就好 if (!ex.Message.ContainsF("mcimirror")) exs += ex.Message + "\r\n"; } throw new Exception(exs); } /// /// 非泛型版本的 DlModRequest,返回 string /// 对可能涉及 Mod 镜像源的请求进行处理。 /// 调用 NetRequest,会进行重试。 /// public static string DlModRequest(string url, string method, string data, string contentType, bool allowMirror = false) => DlModRequest(url, method, data, contentType, allowMirror); /// /// 对可能涉及 Mod 镜像源的请求进行处理。 /// 调用 NetRequest,会进行重试。 /// public static T DlModRequest(string url, string method, string data, string contentType, bool allowMirror = false) { var urls = new List>(); var mcimUrl = DlSourceModGet(url); if ((mcimUrl ?? "") != (url ?? "")) switch (allowMirror ? Config.Download.Comp.CompSourceSolution : 2) { case 0: { urls.Add(new KeyValuePair(mcimUrl, 5)); urls.Add(new KeyValuePair(mcimUrl, 10)); urls.Add(new KeyValuePair(url, 15)); break; } case 1: { urls.Add(new KeyValuePair(url, 5)); urls.Add(new KeyValuePair(mcimUrl, 5)); urls.Add(new KeyValuePair(url, 15)); urls.Add(new KeyValuePair(mcimUrl, 10)); break; } default: { urls.Add(new KeyValuePair(url, 5)); urls.Add(new KeyValuePair(url, 15)); urls.Add(new KeyValuePair(mcimUrl, 10)); break; } } var exs = ""; foreach (var Source in urls) try { string json = Requester.Fetch(Source.Key, new FetchParam { Method = method, Content = data, ContentType = contentType, Timeout = Source.Value * 1000 }); if (typeof(T) == typeof(string)) return (T)(object)json; // 沟槽的,为什么不能写 T is string return (T)(object)ModBase.GetJson(json); } catch (Exception ex) { if (!ex.Message.ContainsF("mcimirror")) exs += ex.Message + "\r\n"; } throw new Exception(exs); } #endregion #region DlSource | 镜像下载源 private static bool dlPreferMojang; /// /// 下载文件(而非获取版本列表)的时候,是否优先使用官方源。 /// public static bool DlSourcePreferMojang => Config.Download.FileSource == 2 || (Config.Download.FileSource == 1 && dlPreferMojang); /// /// 下载文件(而非获取版本列表)的时候,根据是否优先使用官方源决定使用 Url 的顺序。 /// public static IEnumerable DlSourceOrder(IEnumerable officialUrls, IEnumerable mirrorUrls) { return DlSourcePreferMojang ? officialUrls.Union(mirrorUrls) : mirrorUrls.Union(officialUrls); } /// /// 获取版本列表(而非下载文件)的时候,是否优先使用官方源。 /// public static bool DlVersionListPreferMojang => Config.Download.VersionListSource == 2 || (Config.Download.VersionListSource == 1 && dlPreferMojang); /// /// 获取版本列表(而非下载文件)的时候,根据是否优先使用官方源决定使用 Url 的顺序。 /// public static IEnumerable DlVersionListOrder(IEnumerable officialUrls, IEnumerable mirrorUrls) { return DlVersionListPreferMojang ? officialUrls.Union(mirrorUrls) : mirrorUrls.Union(officialUrls); } /// /// 下载 Assets 文件。 /// public static IEnumerable DlSourceAssetsGet(string original) { original = original.Replace("http://resources.download.minecraft.net", "https://resources.download.minecraft.net"); return DlSourceOrder(new[] { original }, new[] { original.Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/assets") .Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/assets") .Replace("https://resources.download.minecraft.net", "https://bmclapi2.bangbang93.com/assets") }); } /// /// 下载 Libraries 文件。 /// public static IEnumerable DlSourceLibraryGet(string original) { if (new[] { "minecraftforge", "fabricmc", "neoforged" }.Any(k => original.Contains(k))) // 不添加原版源 return new[] { original.Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/maven") .Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/maven") .Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/maven") .Replace("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto"), original.Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/libraries") .Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/libraries") .Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries") .Replace("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto") }; return DlSourceOrder(new[] { original }, new[] { original.Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/maven") .Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/maven") .Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/maven") .Replace("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto"), original.Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/libraries") .Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/libraries") .Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries") .Replace("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto"), original }); } /// /// 下载 Launcher 或 Meta 文件。 /// 不应使用它来获取版本列表(因为它只使用文件下载源设置来决定源顺序)。 /// public static IEnumerable DlSourceLauncherOrMetaGet(string original) { if (original is null) throw new Exception(Lang.Text("Minecraft.Download.Error.NoJsonDownloadAddress")); return DlSourceOrder(new[] { original }, new[] { original.Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com") .Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com") .Replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com") .Replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com") .Replace("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto"), original }); } /// /// Mod Api 镜像源 /// /// /// public static string DlSourceModGet(string original) { return original.Replace("https://api.modrinth.com", "https://mod.mcimirror.top/modrinth") .Replace("https://api.curseforge.com", "https://mod.mcimirror.top/curseforge"); } /// /// Mod 下载镜像源 /// /// /// public static List DlSourceModDownloadGet(string original) { var res = new List(); var mirrorDl = original.Replace("https://cdn.modrinth.com", "https://mod.mcimirror.top") .Replace("https://edge.forgecdn.net", "https://mod.mcimirror.top"); // like https://cdn.modrinth.com/data/P7dR8mSH/versions/X2hTodix/fabric-api-0.129.0%2B1.21.8.jar // like https://edge.forgecdn.net/files/6767/951/jei-1.21.5-neoforge-21.4.0.27.jar switch (Config.Download.Comp.CompSourceSolution) { case 0: // 镜像源 { res.Add(mirrorDl); res.Add(mirrorDl); break; } case 1: // 平衡 { res.Add(original); res.Add(mirrorDl); break; } case 2: // 官方源 { res.Add(original); res.Add(original); // 错误 break; } default: { Config.Download.Comp.CompSourceSolution = 1; res.Add(original); break; } } res.Add(original); return res; } // Loader 自动切换 private static void DlSourceLoader(ModLoader.LoaderTask mainLoader, List, int>> loaderList, bool isForceRestart = false) { var waitCycle = 0; while (true) { // 检查状态 var beforeLoadersAllFailed = true; foreach (var SubLoader in loaderList) { if (waitCycle == 0) // 判断是否可以不加载,直接使用已经加载好的结果 { if (isForceRestart) continue; // 强制刷新,不行 if (SubLoader.Key.input is null ^ mainLoader.input is null || (SubLoader.Key.input is not null && !SubLoader.Key.input.Equals(mainLoader.input))) continue; // 父子加载器的输入不一样,也不行 } if (SubLoader.Key.State != ModBase.LoadState.Failed) beforeLoadersAllFailed = false; if (SubLoader.Key.State == ModBase.LoadState.Finished) { // 检查加载器成功 mainLoader.output = SubLoader.Key.output; DlSourceLoaderAbort(loaderList); return; } if (beforeLoadersAllFailed) // 此前的加载器全部失败,直接启动后续加载器 if (waitCycle < SubLoader.Value * 100) waitCycle = SubLoader.Value * 100; } // 第一轮时:既然不直接使用已经加载好的结果,那就启动第一个加载器 if (waitCycle == 0) { loaderList.First().Key.Start(mainLoader.input, isForceRestart); foreach (var Loader in loaderList.Skip(1)) Loader.Key.State = ModBase.LoadState.Waiting; // 将其他源标记为未启动,以确保可以切换下载源(#184) } // 检查加载器失败或超时 for (int i = 0, loopTo = loaderList.Count - 1; i <= loopTo; i++) { if (waitCycle != loaderList[i].Value * 100) continue; if (i < loaderList.Count - 1 && !loaderList.All(l => l.Key.State == ModBase.LoadState.Failed)) { // 若还有下一个源,则启动下一个源 loaderList[i + 1].Key.Start(mainLoader.input, isForceRestart); } else { // 若没有,则失败 Exception errorInfo = null; for (int ii = 0, loopTo1 = loaderList.Count - 1; ii <= loopTo1; ii++) { loaderList[ii].Key.input = default; // 重置输入,以免以同样的输入“重试加载”时直接失败 if (loaderList[ii].Key.Error is null) continue; if (errorInfo is null || loaderList[ii].Key.Error.Message .Contains(Lang.Text("Minecraft.Download.Error.NotFound"))) errorInfo = loaderList[ii].Key.Error; } errorInfo ??= new TimeoutException(Lang.Text("Minecraft.Download.Error.Timeout")); DlSourceLoaderAbort(loaderList); throw errorInfo; } break; } // 计时 Thread.Sleep(10); waitCycle += 1; // 检查父加载器中断 if (mainLoader.IsAborted) { DlSourceLoaderAbort(loaderList); return; } } } private static void DlSourceLoaderAbort( List, int>> loaderList) { foreach (var Loader in loaderList) if (Loader.Key.State == ModBase.LoadState.Loading) Loader.Key.Abort(); } #endregion #region DlLegacyFabricList | LegacyFabric 列表 public struct DlLegacyFabricListResult { /// /// 数据来源名称,如“Official”,“BMCLAPI”。 /// public string sourceName; /// /// 是否为官方的实时数据。 /// public bool isOfficial; /// /// 获取到的数据。 /// public JsonObject Value; } /// /// LegacyFabric 列表,主加载器。 /// public static ModLoader.LoaderTask dlLegacyFabricListLoader = new("DlLegacyFabricList Main", DlLegacyFabricListMain); private static void DlLegacyFabricListMain(ModLoader.LoaderTask loader) { switch (Config.Download.VersionListSource) { case 0: { DlSourceLoader(loader, new List, int>> { new(dlLegacyFabricListOfficialLoader, 30) }, loader.isForceRestarting); break; } case 1: { DlSourceLoader(loader, new List, int>> { new(dlLegacyFabricListOfficialLoader, 5) }, loader.isForceRestarting); break; } default: { DlSourceLoader(loader, new List, int>> { new(dlLegacyFabricListOfficialLoader, 60) }, loader.isForceRestarting); break; } } } /// /// LegacyFabric 列表,官方源。 /// public static ModLoader.LoaderTask dlLegacyFabricListOfficialLoader = new("DlLegacyFabricList Official", DlLegacyFabricListOfficialMain); private static void DlLegacyFabricListOfficialMain(ModLoader.LoaderTask loader) { var result = (JsonObject)Requester.FetchJson("https://meta.legacyfabric.net/v2/versions"); try { var output = new DlLegacyFabricListResult { isOfficial = true, sourceName = Lang.Text("Download.Source.LegacyFabricOfficial"), Value = result }; if (output.Value["game"] is null || output.Value["loader"] is null || output.Value["installer"] is null) throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "LegacyFabric", result)); loader.output = output; } catch (Exception ex) { throw new Exception( Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "LegacyFabric", result), ex); } } /// /// Legacy Fabric API 列表,官方源。 /// public static ModLoader.LoaderTask> dlLegacyFabricApiLoader = new("Legacy Fabric API List Loader", task => task.output = ModComp.CompFilesGet("legacy-fabric-api", false)); #endregion /// /// 发送 Minecraft 更新提示。 /// public static void McDownloadClientUpdateHint(string versionName, JsonObject json) { try { // 获取对应版本 JsonNode version = null; foreach (var Token in json["versions"].AsArray()) if (Token["id"] is not null && (Token["id"].ToString() ?? "") == (versionName ?? "")) { version = Token; break; } // 进行提示 if (version is null) return; var time = version["releaseTime"].ToObject(); var msgBoxText = Lang.Text("Minecraft.Update.NewVersion", versionName) + "\r\n" + ((DateTime.Now - time).TotalDays > 1d ? Lang.Text("Minecraft.Update.UpdateTime") + Lang.Date(time) : Lang.Text("Minecraft.Update.UpdatedAt") + Lang.TimeSpan(time - DateTime.Now)); var msgResult = ModMain.MyMsgBox(msgBoxText, Lang.Text("Minecraft.Update.Title"), Lang.Text("Common.Action.Confirm"), Lang.Text("Common.Action.Download"), (DateTime.Now - time).TotalHours > 3d ? Lang.Text("Common.Action.UpdateLog") : "", button3Action: () => ModDownloadLib.McUpdateLogShow(version)); // 弹窗结果 if (msgResult == 2) // 下载 ModBase.RunInUi(() => { PageDownloadInstall.mcVersionWaitingForSelect = versionName; ModMain.frmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadInstall); }); } catch (Exception ex) { ModBase.Log( ex, Lang.Text("Minecraft.Error.UpdateNotify", versionName ?? "Nothing"), ModBase.LogLevel.Feedback, userSummary: Lang.Text("Minecraft.Error.UpdateNotify", versionName ?? "Nothing")); } } }