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"));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|