CI / Go Backend (push) Canceled after 0s
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
2503 lines
100 KiB
C#
2503 lines
100 KiB
C#
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 客户端
|
||
|
||
/// <summary>
|
||
/// 返回某 Minecraft 版本对应的原版主 Jar 文件的下载信息,要求对应依赖实例已存在。
|
||
/// 失败则抛出异常,不需要下载则返回 Nothing。
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 返回某 Minecraft 版本对应的原版主 AssetIndex 文件的下载信息,要求对应依赖实例已存在。
|
||
/// 若未找到,则会返回 Legacy 资源文件或 Nothing。
|
||
/// </summary>
|
||
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));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构造补全某 Minecraft 版本的所有文件的加载器列表。失败会抛出异常。
|
||
/// </summary>
|
||
public static List<ModLoader.LoaderBase> DlClientFix(McInstance version, bool checkAssetsHash,
|
||
AssetsIndexExistsBehaviour assetsIndexBehaviour)
|
||
{
|
||
var loaders = new List<ModLoader.LoaderBase>();
|
||
|
||
#region 下载支持库文件
|
||
|
||
if (ModLibrary.ShouldIgnoreFileCheck(version))
|
||
{
|
||
ModBase.Log("[Download] 已跳过所有 Libraries 检查");
|
||
}
|
||
else
|
||
{
|
||
var loadersLib = new List<ModLoader.LoaderBase>
|
||
{
|
||
new ModLoader.LoaderTask<string, List<DownloadFile>>(
|
||
Lang.Text("Minecraft.Download.Stage.AnalyzeMissingLibraries"),
|
||
task => task.output = ModLibrary.McLibNetFilesFromInstance(version)) { ProgressWeight = 1d },
|
||
new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadLibraries"), new List<DownloadFile>())
|
||
{ ProgressWeight = 15d }
|
||
};
|
||
// 构造加载器
|
||
loaders.Add(
|
||
new ModLoader.LoaderCombo<string>(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<ModLoader.LoaderBase>();
|
||
// 获取资源文件索引地址
|
||
loadersAssets.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
|
||
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<DownloadFile>();
|
||
else
|
||
task.output = new List<DownloadFile> { 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<DownloadFile>())
|
||
{ ProgressWeight = 2d });
|
||
// 要求独立更新索引
|
||
if (assetsIndexBehaviour == AssetsIndexExistsBehaviour.DownloadInBackground)
|
||
{
|
||
var loadersAssetsUpdate = new List<ModLoader.LoaderBase>();
|
||
string tempAddress = null;
|
||
string realAddress = null;
|
||
loadersAssetsUpdate.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
|
||
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<DownloadFile> { 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<DownloadFile>()));
|
||
loadersAssetsUpdate.Add(new ModLoader.LoaderTask<List<DownloadFile>, string>(
|
||
Lang.Text("Minecraft.Download.Stage.CopyAssetsIndex.Background"), task =>
|
||
{
|
||
ModBase.CopyFile(tempAddress, realAddress);
|
||
ModLaunch.McLaunchLog("后台更新资源文件索引成功:" + tempAddress);
|
||
}));
|
||
var updater = new ModLoader.LoaderCombo<string>(
|
||
Lang.Text("Minecraft.Download.Stage.UpdateAssetsIndex.Background"), loadersAssetsUpdate);
|
||
ModBase.Log("[Download] 开始后台检查资源文件索引");
|
||
updater.Start();
|
||
}
|
||
|
||
// 获取资源文件地址
|
||
loadersAssets.Add(new ModLoader.LoaderTask<string, List<DownloadFile>>(
|
||
Lang.Text("Minecraft.Download.Stage.AnalyzeMissingAssets"), task =>
|
||
{
|
||
ModLoader.LoaderBase argprogressFeed = task;
|
||
task.output = ModAssets.McAssetsFixList(version, checkAssetsHash, ref argprogressFeed);
|
||
task = (ModLoader.LoaderTask<string, List<DownloadFile>>)argprogressFeed;
|
||
})
|
||
{
|
||
ProgressWeight = 3d
|
||
});
|
||
// 下载资源文件
|
||
loadersAssets.Add(
|
||
new LoaderDownload(Lang.Text("Minecraft.Download.Stage.DownloadAssets"), new List<DownloadFile>())
|
||
{ ProgressWeight = 25d });
|
||
// 构造加载器
|
||
loaders.Add(
|
||
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Download.Stage.DownloadAssets.MainLoader"),
|
||
loadersAssets)
|
||
{ block = false, show = false, ProgressWeight = 30.5d });
|
||
}
|
||
|
||
#endregion
|
||
|
||
return loaders;
|
||
}
|
||
|
||
public enum AssetsIndexExistsBehaviour
|
||
{
|
||
/// <summary>
|
||
/// 如果文件存在,则不进行下载。
|
||
/// </summary>
|
||
DontDownload,
|
||
|
||
/// <summary>
|
||
/// 如果文件存在,则启动新的下载加载器进行独立的更新。
|
||
/// </summary>
|
||
DownloadInBackground,
|
||
|
||
/// <summary>
|
||
/// 如果文件存在,也同样进行下载。
|
||
/// </summary>
|
||
AlwaysDownload
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region DlClientList | Minecraft 客户端 版本列表
|
||
|
||
/// <summary>
|
||
/// 所有正式版的 Minecraft Drop 序数。
|
||
/// 若从未完成过获取,返回 Nothing;否则必定存在元素,且从高到低排列。
|
||
/// </summary>
|
||
public static List<int> AllDrops
|
||
{
|
||
get
|
||
{
|
||
lock (_allDropsLock)
|
||
{
|
||
if (field is null)
|
||
{
|
||
var rawData = States.Game.Drops;
|
||
if (string.IsNullOrEmpty(rawData))
|
||
field = new List<int>();
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// 数据来源名称,如“Mojang”,“BMCLAPI”。
|
||
/// </summary>
|
||
public string SourceName;
|
||
|
||
/// <summary>
|
||
/// 是否为官方的实时数据。
|
||
/// </summary>
|
||
public bool IsOfficial;
|
||
|
||
/// <summary>
|
||
/// 获取到的 Json 数据。
|
||
/// </summary>
|
||
public JsonObject Value;
|
||
// ''' <summary>
|
||
// ''' 官方源的失败原因。若没有则为 Nothing。
|
||
// ''' </summary>
|
||
// Public OfficialError As Exception
|
||
}
|
||
|
||
/// <summary>
|
||
/// Minecraft 客户端 版本列表,主加载器。
|
||
/// 若要求镜像源必须包含某个版本,则将该版本 ID 作为输入(#5195)。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<string, DlClientListResult> dlClientListLoader =
|
||
new("DlClientList Main", DlClientListMain);
|
||
|
||
private static void DlClientListMain(ModLoader.LoaderTask<string, DlClientListResult> loader)
|
||
{
|
||
switch (Config.Download.VersionListSource)
|
||
{
|
||
case 0:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<string, DlClientListResult>, int>>
|
||
{ new(dlClientListBmclapiLoader, 30), new(dlClientListMojangLoader, 30 + 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<string, DlClientListResult>, int>>
|
||
{ new(dlClientListMojangLoader, 5), new(dlClientListBmclapiLoader, 5 + 30) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<string, DlClientListResult>, int>>
|
||
{ new(dlClientListMojangLoader, 60), new(dlClientListBmclapiLoader, 60 + 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 提取所有 Drop 序数
|
||
var drops = new List<int>();
|
||
foreach (JsonObject version in loader.output.Value["versions"].AsArray())
|
||
drops.Add(McInstanceInfo.VersionToDrop((string)version["id"]));
|
||
AllDrops = drops.Distinct().OrderByDescending(d => d).ToList();
|
||
}
|
||
|
||
// 各个下载源的分加载器
|
||
/// <summary>
|
||
/// Minecraft 客户端 版本列表,Mojang 官方源加载器。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<string, DlClientListResult> dlClientListMojangLoader =
|
||
new("DlClientList Mojang", DlClientListMojangMain);
|
||
|
||
private static bool isNewClientVersionHinted = false;
|
||
|
||
// MC 更新提示
|
||
private static bool _DlClientListMojangMain_IsHinted;
|
||
|
||
private static void DlClientListMojangMain(ModLoader.LoaderTask<string, DlClientListResult> 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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Minecraft 客户端 版本列表,BMCLAPI 源加载器。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<string, DlClientListResult> dlClientListBmclapiLoader =
|
||
new("DlClientList Bmclapi", DlClientListBmclapiMain);
|
||
|
||
private static void DlClientListBmclapiMain(ModLoader.LoaderTask<string, DlClientListResult> 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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取某个版本的 Json 下载地址,若失败则返回 Nothing。必须在工作线程执行。
|
||
/// </summary>
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// 数据来源名称,如“Official”,“BMCLAPI”。
|
||
/// </summary>
|
||
public string sourceName;
|
||
|
||
/// <summary>
|
||
/// 是否为官方的实时数据。
|
||
/// </summary>
|
||
public bool isOfficial;
|
||
|
||
/// <summary>
|
||
/// 获取到的数据。
|
||
/// </summary>
|
||
public List<DlOptiFineListEntry> Value;
|
||
}
|
||
|
||
public class DlOptiFineListEntry
|
||
{
|
||
/// <summary>
|
||
/// 显示名称,已去除 HD_U 字样,如“1.12.2 C8”。
|
||
/// </summary>
|
||
public string DisplayName;
|
||
|
||
/// <summary>
|
||
/// 是否为测试版。
|
||
/// </summary>
|
||
public bool IsPreview;
|
||
|
||
/// <summary>
|
||
/// 原始文件名称,如“preview_OptiFine_1.11_HD_U_E1_pre.jar”。
|
||
/// </summary>
|
||
public string NameFile;
|
||
|
||
/// <summary>
|
||
/// 对应的版本名称,如“1.13.2-OptiFine_HD_U_E6”。
|
||
/// </summary>
|
||
public string NameVersion;
|
||
|
||
/// <summary>
|
||
/// 发布时间,格式为“yyyy/mm/dd”。OptiFine 源无此数据。
|
||
/// </summary>
|
||
public string ReleaseTime;
|
||
|
||
/// <summary>
|
||
/// 需要的最低 Forge 版本。空字符串为无限制,Nothing 为不兼容,“28.1.56” 表示版本号,“1161” 表示版本号的最后一位。
|
||
/// </summary>
|
||
public string RequiredForgeVersion;
|
||
|
||
/// <summary>
|
||
/// 对应的 Minecraft 版本,如“1.12.2”。
|
||
/// </summary>
|
||
public string Inherit
|
||
{
|
||
get => field;
|
||
set
|
||
{
|
||
if (value.EndsWithF(".0"))
|
||
value = value.Substring(0, value.Length - 2);
|
||
field = value;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// OptiFine 版本列表,主加载器。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlOptiFineListResult> dlOptiFineListLoader =
|
||
new("DlOptiFineList Main", DlOptiFineListMain);
|
||
|
||
private static void DlOptiFineListMain(ModLoader.LoaderTask<int, DlOptiFineListResult> loader)
|
||
{
|
||
switch (Config.Download.VersionListSource)
|
||
{
|
||
case 0:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlOptiFineListResult>, int>>
|
||
{ new(dlOptiFineListBmclapiLoader, 30), new(dlOptiFineListOfficialLoader, 30 + 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlOptiFineListResult>, int>>
|
||
{ new(dlOptiFineListOfficialLoader, 5), new(dlOptiFineListBmclapiLoader, 5 + 30) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlOptiFineListResult>, int>>
|
||
{ new(dlOptiFineListOfficialLoader, 60), new(dlOptiFineListBmclapiLoader, 60 + 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// OptiFine 版本列表,官方源。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlOptiFineListResult> dlOptiFineListOfficialLoader =
|
||
new("DlOptiFineList Official", DlOptiFineListOfficialMain);
|
||
|
||
private static void DlOptiFineListOfficialMain(ModLoader.LoaderTask<int, DlOptiFineListResult> 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<DlOptiFineListEntry>();
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// OptiFine 版本列表,BMCLAPI。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlOptiFineListResult> dlOptiFineListBmclapiLoader =
|
||
new("DlOptiFineList Bmclapi", DlOptiFineListBmclapiMain);
|
||
|
||
private static void DlOptiFineListBmclapiMain(ModLoader.LoaderTask<int, DlOptiFineListResult> loader)
|
||
{
|
||
var json = (JsonArray)Requester.FetchJson("https://bmclapi2.bangbang93.com/optifine/versionList");
|
||
try
|
||
{
|
||
var versions = new List<DlOptiFineListEntry>();
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// 数据来源名称,如“Official”,“BMCLAPI”。
|
||
/// </summary>
|
||
public string sourceName;
|
||
|
||
/// <summary>
|
||
/// 是否为官方的实时数据。
|
||
/// </summary>
|
||
public bool isOfficial;
|
||
|
||
/// <summary>
|
||
/// 获取到的数据。
|
||
/// </summary>
|
||
public List<string> Value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Forge 版本列表,主加载器。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlForgeListResult> dlForgeListLoader =
|
||
new("DlForgeList Main", DlForgeListMain);
|
||
|
||
private static void DlForgeListMain(ModLoader.LoaderTask<int, DlForgeListResult> loader)
|
||
{
|
||
switch (Config.Download.VersionListSource)
|
||
{
|
||
case 0:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlForgeListResult>, int>>
|
||
{ new(dlForgeListBmclapiLoader, 30), new(dlForgeListOfficialLoader, 30 + 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlForgeListResult>, int>>
|
||
{ new(dlForgeListOfficialLoader, 5), new(dlForgeListBmclapiLoader, 5 + 30) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlForgeListResult>, int>>
|
||
{ new(dlForgeListOfficialLoader, 60), new(dlForgeListBmclapiLoader, 60 + 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Forge 版本列表,官方源。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlForgeListResult> dlForgeListOfficialLoader =
|
||
new("DlForgeList Official", DlForgeListOfficialMain);
|
||
|
||
private static void DlForgeListOfficialMain(ModLoader.LoaderTask<int, DlForgeListResult> 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 };
|
||
}
|
||
|
||
/// <summary>
|
||
/// Forge 版本列表,BMCLAPI。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlForgeListResult> dlForgeListBmclapiLoader =
|
||
new("DlForgeList Bmclapi", DlForgeListBmclapiMain);
|
||
|
||
private static void DlForgeListBmclapiMain(ModLoader.LoaderTask<int, DlForgeListResult> 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<DlForgelikeEntry>
|
||
{
|
||
public enum ForgelikeType
|
||
{
|
||
Forge,
|
||
NeoForge,
|
||
Cleanroom
|
||
}
|
||
|
||
/// <summary>
|
||
/// Forgelike 种类。Forge、NeoForge、Cleanroom。
|
||
/// </summary>
|
||
public ForgelikeType forgeType;
|
||
|
||
/// <summary>
|
||
/// 对应的 Minecraft 版本,如“1.12.2”。
|
||
/// </summary>
|
||
public string Inherit;
|
||
|
||
/// <summary>
|
||
/// 标准化后的版本号,仅可用于比较与排序。
|
||
/// 格式: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)。
|
||
/// </summary>
|
||
public Version version;
|
||
|
||
/// <summary>
|
||
/// 可对玩家显示的非格式化版本名。
|
||
/// Forge:如 “50.1.9”、“14.22.1.2478”(Legacy)。
|
||
/// NeoForge:如 “20.4.30-beta”、“47.1.99”(Legacy)。
|
||
/// Cleanroom:如 “0.2.4-alpha”。
|
||
/// </summary>
|
||
public string VersionName;
|
||
|
||
/// <summary>
|
||
/// 加载器名称。Forge / NeoForge / Cleanroom。
|
||
/// </summary>
|
||
public string LoaderName => forgeType.ToString();
|
||
|
||
/// <summary>
|
||
/// 文件扩展名。不以小数点开头。
|
||
/// </summary>
|
||
public string FileExtension
|
||
{
|
||
get
|
||
{
|
||
if (forgeType == 0) return ((DlForgeVersionEntry)this).Category == "installer" ? "jar" : "zip";
|
||
|
||
return "jar";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Forge:MC 版本是否小于 1.13。
|
||
/// NeoForge:MC 版本是否为 1.20.1。
|
||
/// Cleanroom:固定为 False。
|
||
/// </summary>
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// 安装类型。有 installer、client、universal 三种。
|
||
/// </summary>
|
||
public string Category;
|
||
|
||
/// <summary>
|
||
/// 用于下载的文件版本名。可能在 Version 的基础上添加了分支。
|
||
/// </summary>
|
||
public string FileVersion;
|
||
|
||
/// <summary>
|
||
/// 文件的 MD5 或 SHA1(BMCLAPI 的老版本是 MD5,新版本是 SHA1;官方源总是 MD5)。
|
||
/// </summary>
|
||
public string Hash;
|
||
|
||
/// <summary>
|
||
/// 是否为推荐版本。
|
||
/// </summary>
|
||
public bool IsRecommended;
|
||
|
||
/// <summary>
|
||
/// 发布时间,格式为“yyyy/MM/dd HH:mm”。
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Forge 版本列表,主加载器。
|
||
/// </summary>
|
||
public static void DlForgeVersionMain(ModLoader.LoaderTask<string, List<DlForgeVersionEntry>> loader)
|
||
{
|
||
var dlForgeVersionOfficialLoader =
|
||
new ModLoader.LoaderTask<string, List<DlForgeVersionEntry>>("DlForgeVersion Official",
|
||
DlForgeVersionOfficialMain);
|
||
var dlForgeVersionBmclapiLoader =
|
||
new ModLoader.LoaderTask<string, List<DlForgeVersionEntry>>("DlForgeVersion Bmclapi",
|
||
DlForgeVersionBmclapiMain);
|
||
switch (Config.Download.VersionListSource)
|
||
{
|
||
case 0:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<string, List<DlForgeVersionEntry>>, int>>
|
||
{ new(dlForgeVersionBmclapiLoader, 30), new(dlForgeVersionOfficialLoader, 30 + 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<string, List<DlForgeVersionEntry>>, int>>
|
||
{ new(dlForgeVersionOfficialLoader, 5), new(dlForgeVersionBmclapiLoader, 5 + 30) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<string, List<DlForgeVersionEntry>>, int>>
|
||
{ new(dlForgeVersionOfficialLoader, 60), new(dlForgeVersionBmclapiLoader, 60 + 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Forge 版本列表,官方源。
|
||
/// </summary>
|
||
public static void DlForgeVersionOfficialMain(ModLoader.LoaderTask<string, List<DlForgeVersionEntry>> 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<DlForgeVersionEntry>();
|
||
try
|
||
{
|
||
// 分割版本信息
|
||
var versionCodes = result.Substring(0, result.LastIndexOfF("</table>"))
|
||
.Split("<td class=\"download-version");
|
||
// 获取所有版本信息
|
||
for (int i = 1, loopTo = versionCodes.Count() - 1; i <= loopTo; i++)
|
||
{
|
||
var versionCode = versionCodes[i];
|
||
try
|
||
{
|
||
// 基础信息获取
|
||
var name = versionCode.RegexSeek(@"(?<=[^(0-9)]+)[0-9\.]+");
|
||
var isRecommended = versionCode.Contains("fa promo-recommended");
|
||
var inherit = loader.input;
|
||
// 分支获取
|
||
var branch = versionCode.RegexSeek($"(?<=-{name}-)[^-\"]+(?=-[a-z]+.[a-z]{{3}})");
|
||
if (string.IsNullOrWhiteSpace(branch))
|
||
branch = null;
|
||
// 发布时间获取
|
||
var releaseTimeOriginal = versionCode.RegexSeek("(?<=\"download-time\" title=\")[^\"]+");
|
||
// Dim ReleaseTimeSplit = ReleaseTimeOriginal.Split(" -:".ToCharArray) '原格式:"2021-02-15 03:24:02"
|
||
var releaseDate =
|
||
DateTime.Parse(releaseTimeOriginal, null, DateTimeStyles.AssumeUniversal); // 以 UTC 时间作为标准
|
||
var releaseTime = Lang.Date(releaseDate.ToLocalTime(), "g"); // 时区与格式转换
|
||
// 分类与 MD5 获取
|
||
string mD5;
|
||
string category;
|
||
if (versionCode.Contains("classifier-installer\""))
|
||
{
|
||
// 类型为 installer.jar,支持范围 ~753 (~ 1.6.1 部分), 738~684 (1.5.2 全部)
|
||
versionCode = versionCode.Substring(versionCode.IndexOfF("installer.jar"));
|
||
mD5 = versionCode.RegexSeek("(?<=MD5:</strong> )[^<]+");
|
||
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:</strong> )[^<]+");
|
||
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:</strong> )[^<]+");
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Forge 版本列表,BMCLAPI。
|
||
/// </summary>
|
||
public static void DlForgeVersionBmclapiMain(ModLoader.LoaderTask<string, List<DlForgeVersionEntry>> 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<DlForgeVersionEntry>();
|
||
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<DateTime>().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
|
||
{
|
||
/// <summary>
|
||
/// 数据来源名称,如“Official”,“BMCLAPI”。
|
||
/// </summary>
|
||
public string sourceName;
|
||
|
||
/// <summary>
|
||
/// 是否为官方的实时数据。
|
||
/// </summary>
|
||
public bool isOfficial;
|
||
|
||
/// <summary>
|
||
/// 所有版本的列表。已经按从新到老排序。
|
||
/// </summary>
|
||
public List<DlNeoForgeListEntry> Value;
|
||
}
|
||
|
||
public class DlNeoForgeListEntry : DlForgelikeEntry
|
||
{
|
||
/// <summary>
|
||
/// API 使用的原始版本字符串,如 “20.4.30-beta”、“1.20.1-47.1.99”(Legacy)。
|
||
/// </summary>
|
||
public string ApiName;
|
||
|
||
/// <summary>
|
||
/// 是否是 Beta 版。
|
||
/// </summary>
|
||
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("+");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 文件在官网的基础地址,不包含后缀。
|
||
/// </summary>
|
||
public string UrlBase
|
||
{
|
||
get
|
||
{
|
||
var packageName = IsLegacy ? "forge" : "neoforge";
|
||
return
|
||
$"https://maven.neoforged.net/releases/net/neoforged/{packageName}/{ApiName}/{packageName}-{ApiName}";
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// NeoForge 版本列表,主加载器。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlNeoForgeListResult> dlNeoForgeListLoader =
|
||
new("DlNeoForgeList Main", DlNeoForgeListMain);
|
||
|
||
private static void DlNeoForgeListMain(ModLoader.LoaderTask<int, DlNeoForgeListResult> loader)
|
||
{
|
||
switch (Config.Download.VersionListSource)
|
||
{
|
||
case 0:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlNeoForgeListResult>, int>>
|
||
{ new(dlNeoForgeListBmclapiLoader, 30), new(dlNeoForgeListOfficialLoader, 30 + 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlNeoForgeListResult>, int>>
|
||
{ new(dlNeoForgeListOfficialLoader, 5), new(dlNeoForgeListBmclapiLoader, 5 + 30) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlNeoForgeListResult>, int>>
|
||
{ new(dlNeoForgeListOfficialLoader, 60), new(dlNeoForgeListBmclapiLoader, 60 + 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// NeoForge 版本列表,官方源。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlNeoForgeListResult> dlNeoForgeListOfficialLoader =
|
||
new("DlNeoForgeList Official", DlNeoForgeListOfficialMain);
|
||
|
||
private static void DlNeoForgeListOfficialMain(ModLoader.LoaderTask<int, DlNeoForgeListResult> 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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// NeoForge 版本列表,BMCLAPI。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlNeoForgeListResult> dlNeoForgeListBmclapiLoader =
|
||
new("DlNeoForgeList Bmclapi", DlNeoForgeListBmclapiMain);
|
||
|
||
public static void DlNeoForgeListBmclapiMain(ModLoader.LoaderTask<int, DlNeoForgeListResult> 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<DlNeoForgeListEntry> 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
|
||
{
|
||
/// <summary>
|
||
/// 数据来源名称,如“Official”,“BMCLAPI”。
|
||
/// </summary>
|
||
public string sourceName;
|
||
|
||
/// <summary>
|
||
/// 是否为官方的实时数据。
|
||
/// </summary>
|
||
public bool isOfficial;
|
||
|
||
/// <summary>
|
||
/// 所有版本的列表。已经按从新到老排序。
|
||
/// </summary>
|
||
public List<DlCleanroomListEntry> Value;
|
||
}
|
||
|
||
public class DlCleanroomListEntry : DlForgelikeEntry
|
||
{
|
||
/// <summary>
|
||
/// API 使用的原始版本字符串,如 “0.2.4-alpha”。
|
||
/// </summary>
|
||
public string ApiName;
|
||
|
||
/// <summary>
|
||
/// 是否是 Beta 版。
|
||
/// </summary>
|
||
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";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 文件在官网的基础地址,不包含后缀。
|
||
/// </summary>
|
||
public string UrlBase =>
|
||
$"https://github.com/CleanroomMC/Cleanroom/releases/download/{ApiName}/cleanroom-{ApiName}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// Cleanroom 版本列表,主加载器。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlCleanroomListResult> dlCleanroomListLoader =
|
||
new("DlCleanroomList Main", DlCleanroomListMain);
|
||
|
||
private static void DlCleanroomListMain(ModLoader.LoaderTask<int, DlCleanroomListResult> loader)
|
||
{
|
||
switch (Config.Download.VersionListSource)
|
||
{
|
||
case 0:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlCleanroomListResult>, int>>
|
||
{ new(dlCleanroomListOfficialLoader, 30) }, loader.isForceRestarting);
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlCleanroomListResult>, int>>
|
||
{ new(dlCleanroomListOfficialLoader, 5) }, loader.isForceRestarting);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlCleanroomListResult>, int>>
|
||
{ new(dlCleanroomListOfficialLoader, 60) }, loader.isForceRestarting);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Cleanroom 版本列表,官方源。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlCleanroomListResult> dlCleanroomListOfficialLoader =
|
||
new("DlCleanroomList Official", DlCleanroomListOfficialMain);
|
||
|
||
private static void DlCleanroomListOfficialMain(ModLoader.LoaderTask<int, DlCleanroomListResult> 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<DlCleanroomListEntry> GetCleanroomEntries(string latestJson)
|
||
{
|
||
var versions = new List<DlCleanroomListEntry>();
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// 数据来源名称,如“Official”,“BMCLAPI”。
|
||
/// </summary>
|
||
public string sourceName;
|
||
|
||
/// <summary>
|
||
/// 是否为官方的实时数据。
|
||
/// </summary>
|
||
public bool isOfficial;
|
||
|
||
/// <summary>
|
||
/// 获取到的数据。
|
||
/// </summary>
|
||
public List<DlLiteLoaderListEntry> Value;
|
||
|
||
/// <summary>
|
||
/// 官方源的失败原因。若没有则为 Nothing。
|
||
/// </summary>
|
||
public Exception officialError;
|
||
}
|
||
|
||
public class DlLiteLoaderListEntry
|
||
{
|
||
/// <summary>
|
||
/// 实际的文件名,如“liteloader-installer-1.12-00-SNAPSHOT.jar”。
|
||
/// </summary>
|
||
public string FileName;
|
||
|
||
/// <summary>
|
||
/// 对应的 Minecraft 版本,如“1.12.2”。
|
||
/// </summary>
|
||
public string Inherit;
|
||
|
||
/// <summary>
|
||
/// 是否为 1.7 及更早的远古版。
|
||
/// </summary>
|
||
public bool IsLegacy;
|
||
|
||
/// <summary>
|
||
/// 是否为测试版。
|
||
/// </summary>
|
||
public bool IsPreview;
|
||
|
||
/// <summary>
|
||
/// 对应的 Json 项。
|
||
/// </summary>
|
||
public JsonNode jsonToken;
|
||
|
||
/// <summary>
|
||
/// 文件的 MD5。
|
||
/// </summary>
|
||
public string MD5;
|
||
|
||
/// <summary>
|
||
/// 发布时间,格式为“yyyy/mm/dd HH:mm”。
|
||
/// </summary>
|
||
public string ReleaseTime;
|
||
}
|
||
|
||
/// <summary>
|
||
/// LiteLoader 版本列表,主加载器。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlLiteLoaderListResult> dlLiteLoaderListLoader =
|
||
new("DlLiteLoaderList Main", DlLiteLoaderListMain);
|
||
|
||
private static void DlLiteLoaderListMain(ModLoader.LoaderTask<int, DlLiteLoaderListResult> loader)
|
||
{
|
||
switch (Config.Download.VersionListSource)
|
||
{
|
||
case 0:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlLiteLoaderListResult>, int>>
|
||
{
|
||
new(dlLiteLoaderListBmclapiLoader, 30), new(dlLiteLoaderListOfficialLoader, 30 + 60)
|
||
}, loader.isForceRestarting);
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlLiteLoaderListResult>, int>>
|
||
{
|
||
new(dlLiteLoaderListOfficialLoader, 5), new(dlLiteLoaderListBmclapiLoader, 5 + 30)
|
||
}, loader.isForceRestarting);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlLiteLoaderListResult>, int>>
|
||
{
|
||
new(dlLiteLoaderListOfficialLoader, 60), new(dlLiteLoaderListBmclapiLoader, 60 + 60)
|
||
}, loader.isForceRestarting);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// LiteLoader 版本列表,官方源。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlLiteLoaderListResult> dlLiteLoaderListOfficialLoader =
|
||
new("DlLiteLoaderList Official", DlLiteLoaderListOfficialMain);
|
||
|
||
private static void DlLiteLoaderListOfficialMain(ModLoader.LoaderTask<int, DlLiteLoaderListResult> loader)
|
||
{
|
||
var result =
|
||
(JsonObject)Requester.FetchJson("https://dl.liteloader.com/versions/versions.json");
|
||
try
|
||
{
|
||
var json = (JsonObject)result["versions"];
|
||
var versions = new List<DlLiteLoaderListEntry>();
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// LiteLoader 版本列表,BMCLAPI。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlLiteLoaderListResult> dlLiteLoaderListBmclapiLoader =
|
||
new("DlLiteLoaderList Bmclapi", DlLiteLoaderListBmclapiMain);
|
||
|
||
private static void DlLiteLoaderListBmclapiMain(ModLoader.LoaderTask<int, DlLiteLoaderListResult> 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<DlLiteLoaderListEntry>();
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// 数据来源名称,如“Official”,“BMCLAPI”。
|
||
/// </summary>
|
||
public string sourceName;
|
||
|
||
/// <summary>
|
||
/// 是否为官方的实时数据。
|
||
/// </summary>
|
||
public bool isOfficial;
|
||
|
||
/// <summary>
|
||
/// 获取到的数据。
|
||
/// </summary>
|
||
public JsonObject Value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Fabric 列表,主加载器。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlFabricListResult> dlFabricListLoader =
|
||
new("DlFabricList Main", DlFabricListMain);
|
||
|
||
private static void DlFabricListMain(ModLoader.LoaderTask<int, DlFabricListResult> loader)
|
||
{
|
||
switch (Config.Download.VersionListSource)
|
||
{
|
||
case 0:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlFabricListResult>, int>>
|
||
{ new(dlFabricListBmclapiLoader, 30), new(dlFabricListOfficialLoader, 30 + 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlFabricListResult>, int>>
|
||
{ new(dlFabricListOfficialLoader, 5), new(dlFabricListBmclapiLoader, 5 + 30) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlFabricListResult>, int>>
|
||
{ new(dlFabricListOfficialLoader, 60), new(dlFabricListBmclapiLoader, 60 + 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Fabric 列表,官方源。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlFabricListResult> dlFabricListOfficialLoader =
|
||
new("DlFabricList Official", DlFabricListOfficialMain);
|
||
|
||
private static void DlFabricListOfficialMain(ModLoader.LoaderTask<int, DlFabricListResult> 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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Fabric 列表,BMCLAPI。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlFabricListResult> dlFabricListBmclapiLoader =
|
||
new("DlFabricList Bmclapi", DlFabricListBmclapiMain);
|
||
|
||
private static void DlFabricListBmclapiMain(ModLoader.LoaderTask<int, DlFabricListResult> 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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Fabric API 列表,官方源。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, List<ModComp.CompFile>> dlFabricApiLoader = new("Fabric API List Loader",
|
||
task => task.output = ModComp.CompFilesGet("fabric-api", false));
|
||
|
||
/// <summary>
|
||
/// OptiFabric 列表,官方源。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, List<ModComp.CompFile>> dlOptiFabricLoader =
|
||
new("OptiFabric List Loader", task => task.output = ModComp.CompFilesGet("322385", true));
|
||
|
||
#endregion
|
||
|
||
#region DlLabyModList | LabyMod 列表
|
||
|
||
public struct DlLabyModListResult
|
||
{
|
||
/// <summary>
|
||
/// 获取到的数据。
|
||
/// </summary>
|
||
public JsonObject Value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// LabyMod 列表,主加载器。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlLabyModListResult> dlLabyModListLoader =
|
||
new("DlLabyModList Main", DlLabyModListMain);
|
||
|
||
private static void DlLabyModListMain(ModLoader.LoaderTask<int, DlLabyModListResult> loader)
|
||
{
|
||
switch (Config.Download.VersionListSource)
|
||
{
|
||
case 0:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlLabyModListResult>, int>>
|
||
{ new(dlLabyModListOfficialLoader, 30), new(dlLabyModListOfficialLoader, 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlLabyModListResult>, int>>
|
||
{ new(dlLabyModListOfficialLoader, 5), new(dlLabyModListOfficialLoader, 35) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlLabyModListResult>, int>>
|
||
{ new(dlLabyModListOfficialLoader, 60), new(dlLabyModListOfficialLoader, 60) },
|
||
loader.isForceRestarting);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// LabyMod 列表,官方源。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlLabyModListResult> dlLabyModListOfficialLoader =
|
||
new("DlLabyModList Official", DlLabyModListOfficialMain);
|
||
|
||
private static void DlLabyModListOfficialMain(ModLoader.LoaderTask<int, DlLabyModListResult> 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 镜像源请求
|
||
|
||
/// <summary>
|
||
/// 对可能涉及 Mod 镜像源的请求进行处理,返回字符串。
|
||
/// 调用 NetGetCodeByRequest,会进行重试。
|
||
/// </summary>
|
||
public static string DlModRequest(string url) => DlModRequest<string>(url);
|
||
|
||
/// <summary>
|
||
/// 对可能涉及 Mod 镜像源的请求进行处理,返回字符串或 JSON 对象。
|
||
/// 调用 NetGetCodeByRequest,会进行重试。
|
||
/// </summary>
|
||
public static T DlModRequest<T>(string url)
|
||
{
|
||
var urls = new List<KeyValuePair<string, int>>();
|
||
var mcimUrl = DlSourceModGet(url);
|
||
if ((mcimUrl ?? "") != (url ?? ""))
|
||
switch (Config.Download.Comp.CompSourceSolution)
|
||
{
|
||
case 0:
|
||
{
|
||
urls.Add(new KeyValuePair<string, int>(mcimUrl, 5));
|
||
urls.Add(new KeyValuePair<string, int>(mcimUrl, 10));
|
||
urls.Add(new KeyValuePair<string, int>(url, 15));
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
urls.Add(new KeyValuePair<string, int>(url, 5));
|
||
urls.Add(new KeyValuePair<string, int>(mcimUrl, 5));
|
||
urls.Add(new KeyValuePair<string, int>(url, 15));
|
||
urls.Add(new KeyValuePair<string, int>(mcimUrl, 10));
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
urls.Add(new KeyValuePair<string, int>(url, 5));
|
||
urls.Add(new KeyValuePair<string, int>(url, 15));
|
||
urls.Add(new KeyValuePair<string, int>(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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 非泛型版本的 DlModRequest,返回 string
|
||
/// 对可能涉及 Mod 镜像源的请求进行处理。
|
||
/// 调用 NetRequest,会进行重试。
|
||
/// </summary>
|
||
public static string DlModRequest(string url, string method, string data, string contentType,
|
||
bool allowMirror = false) => DlModRequest<string>(url, method, data, contentType, allowMirror);
|
||
|
||
/// <summary>
|
||
/// 对可能涉及 Mod 镜像源的请求进行处理。
|
||
/// 调用 NetRequest,会进行重试。
|
||
/// </summary>
|
||
public static T DlModRequest<T>(string url, string method, string data, string contentType,
|
||
bool allowMirror = false)
|
||
{
|
||
var urls = new List<KeyValuePair<string, int>>();
|
||
var mcimUrl = DlSourceModGet(url);
|
||
if ((mcimUrl ?? "") != (url ?? ""))
|
||
switch (allowMirror ? Config.Download.Comp.CompSourceSolution : 2)
|
||
{
|
||
case 0:
|
||
{
|
||
urls.Add(new KeyValuePair<string, int>(mcimUrl, 5));
|
||
urls.Add(new KeyValuePair<string, int>(mcimUrl, 10));
|
||
urls.Add(new KeyValuePair<string, int>(url, 15));
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
urls.Add(new KeyValuePair<string, int>(url, 5));
|
||
urls.Add(new KeyValuePair<string, int>(mcimUrl, 5));
|
||
urls.Add(new KeyValuePair<string, int>(url, 15));
|
||
urls.Add(new KeyValuePair<string, int>(mcimUrl, 10));
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
urls.Add(new KeyValuePair<string, int>(url, 5));
|
||
urls.Add(new KeyValuePair<string, int>(url, 15));
|
||
urls.Add(new KeyValuePair<string, int>(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;
|
||
|
||
/// <summary>
|
||
/// 下载文件(而非获取版本列表)的时候,是否优先使用官方源。
|
||
/// </summary>
|
||
public static bool DlSourcePreferMojang =>
|
||
Config.Download.FileSource == 2 ||
|
||
(Config.Download.FileSource == 1 && dlPreferMojang);
|
||
|
||
/// <summary>
|
||
/// 下载文件(而非获取版本列表)的时候,根据是否优先使用官方源决定使用 Url 的顺序。
|
||
/// </summary>
|
||
public static IEnumerable<string> DlSourceOrder(IEnumerable<string> officialUrls, IEnumerable<string> mirrorUrls)
|
||
{
|
||
return DlSourcePreferMojang ? officialUrls.Union(mirrorUrls) : mirrorUrls.Union(officialUrls);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取版本列表(而非下载文件)的时候,是否优先使用官方源。
|
||
/// </summary>
|
||
public static bool DlVersionListPreferMojang =>
|
||
Config.Download.VersionListSource == 2 ||
|
||
(Config.Download.VersionListSource == 1 && dlPreferMojang);
|
||
|
||
/// <summary>
|
||
/// 获取版本列表(而非下载文件)的时候,根据是否优先使用官方源决定使用 Url 的顺序。
|
||
/// </summary>
|
||
public static IEnumerable<string> DlVersionListOrder(IEnumerable<string> officialUrls,
|
||
IEnumerable<string> mirrorUrls)
|
||
{
|
||
return DlVersionListPreferMojang ? officialUrls.Union(mirrorUrls) : mirrorUrls.Union(officialUrls);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 下载 Assets 文件。
|
||
/// </summary>
|
||
public static IEnumerable<string> 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")
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 下载 Libraries 文件。
|
||
/// </summary>
|
||
public static IEnumerable<string> 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
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 下载 Launcher 或 Meta 文件。
|
||
/// 不应使用它来获取版本列表(因为它只使用文件下载源设置来决定源顺序)。
|
||
/// </summary>
|
||
public static IEnumerable<string> 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
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// Mod Api 镜像源
|
||
/// </summary>
|
||
/// <param name="original"></param>
|
||
/// <returns></returns>
|
||
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");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Mod 下载镜像源
|
||
/// </summary>
|
||
/// <param name="original"></param>
|
||
/// <returns></returns>
|
||
public static List<string> DlSourceModDownloadGet(string original)
|
||
{
|
||
var res = new List<string>();
|
||
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<InputType, OutputType>(ModLoader.LoaderTask<InputType, OutputType> mainLoader,
|
||
List<KeyValuePair<ModLoader.LoaderTask<InputType, OutputType>, 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<InputType, OutputType>(
|
||
List<KeyValuePair<ModLoader.LoaderTask<InputType, OutputType>, int>> loaderList)
|
||
{
|
||
foreach (var Loader in loaderList)
|
||
if (Loader.Key.State == ModBase.LoadState.Loading)
|
||
Loader.Key.Abort();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region DlLegacyFabricList | LegacyFabric 列表
|
||
|
||
public struct DlLegacyFabricListResult
|
||
{
|
||
/// <summary>
|
||
/// 数据来源名称,如“Official”,“BMCLAPI”。
|
||
/// </summary>
|
||
public string sourceName;
|
||
|
||
/// <summary>
|
||
/// 是否为官方的实时数据。
|
||
/// </summary>
|
||
public bool isOfficial;
|
||
|
||
/// <summary>
|
||
/// 获取到的数据。
|
||
/// </summary>
|
||
public JsonObject Value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// LegacyFabric 列表,主加载器。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlLegacyFabricListResult> dlLegacyFabricListLoader =
|
||
new("DlLegacyFabricList Main", DlLegacyFabricListMain);
|
||
|
||
private static void DlLegacyFabricListMain(ModLoader.LoaderTask<int, DlLegacyFabricListResult> loader)
|
||
{
|
||
switch (Config.Download.VersionListSource)
|
||
{
|
||
case 0:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlLegacyFabricListResult>, int>>
|
||
{ new(dlLegacyFabricListOfficialLoader, 30) }, loader.isForceRestarting);
|
||
break;
|
||
}
|
||
case 1:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlLegacyFabricListResult>, int>>
|
||
{ new(dlLegacyFabricListOfficialLoader, 5) }, loader.isForceRestarting);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
DlSourceLoader(loader,
|
||
new List<KeyValuePair<ModLoader.LoaderTask<int, DlLegacyFabricListResult>, int>>
|
||
{ new(dlLegacyFabricListOfficialLoader, 60) }, loader.isForceRestarting);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// LegacyFabric 列表,官方源。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, DlLegacyFabricListResult> dlLegacyFabricListOfficialLoader =
|
||
new("DlLegacyFabricList Official", DlLegacyFabricListOfficialMain);
|
||
|
||
private static void DlLegacyFabricListOfficialMain(ModLoader.LoaderTask<int, DlLegacyFabricListResult> 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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Legacy Fabric API 列表,官方源。
|
||
/// </summary>
|
||
public static ModLoader.LoaderTask<int, List<ModComp.CompFile>> dlLegacyFabricApiLoader =
|
||
new("Legacy Fabric API List Loader", task => task.output = ModComp.CompFilesGet("legacy-fabric-api", false));
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 发送 Minecraft 更新提示。
|
||
/// </summary>
|
||
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<DateTime>();
|
||
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"));
|
||
}
|
||
}
|
||
}
|