();
try
{
// 分割版本信息
var versionCodes = result.Substring(0, result.LastIndexOfF(""))
.Split("| )[^<]+");
category = "installer";
}
else if (versionCode.Contains("classifier-universal\""))
{
// 类型为 universal.zip,支持范围 751~449 (1.6.1 部分), 682~183 (1.5.1 ~ 1.3.2 部分)
versionCode = versionCode.Substring(versionCode.IndexOfF("universal.zip"));
mD5 = versionCode.RegexSeek("(?<=MD5: )[^<]+");
category = "universal";
}
else if (versionCode.Contains("client.zip"))
{
// 类型为 client.zip,支持范围 182~ (1.3.2 部分 ~)
versionCode = versionCode.Substring(versionCode.IndexOfF("client.zip"));
mD5 = versionCode.RegexSeek("(?<=MD5: )[^<]+");
category = "client";
}
else
{
// 没有任何下载(1.6.4 有一部分这种情况)
continue;
}
// 添加进列表
versions.Add(new DlForgeVersionEntry(name, branch, inherit)
{
Category = category, IsRecommended = isRecommended,
Hash = mD5.Trim('\r', '\n'),
ReleaseTime = releaseTime
});
}
catch (Exception ex)
{
throw new Exception(
Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Forge", versionCode), ex);
}
}
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Forge", result), ex);
}
if (!versions.Any())
throw new Exception(Lang.Text("Minecraft.Download.Error.NotFound"));
loader.output = versions;
}
///
/// Forge 版本列表,BMCLAPI。
///
public static void DlForgeVersionBmclapiMain(ModLoader.LoaderTask> loader)
{
var json = (JsonArray)Requester.FetchJson(
"https://bmclapi2.bangbang93.com/forge/minecraft/" +
loader.input.Replace("-", "_")); // 兼容 Forge 1.7.10-pre4,#4057
var versions = new List();
try
{
var recommended = ModDownloadLib.McDownloadForgeRecommendedGet(loader.input);
foreach (JsonObject Token in json)
{
// 分类与 Hash 获取
string hash = null;
var category = "unknown";
var proi = -1;
foreach (JsonObject File in Token["files"].AsArray())
switch (File["category"].ToString() ?? "")
{
case "installer":
{
if (File["format"].ToString() == "jar")
{
// 类型为 installer.jar,支持范围 ~753 (~ 1.6.1 部分), 738~684 (1.5.2 全部)
hash = (string)File["hash"];
category = "installer";
proi = 2;
}
break;
}
case "universal":
{
if (proi <= 1 && File["format"].ToString() == "zip")
{
// 类型为 universal.zip,支持范围 751~449 (1.6.1 部分), 682~183 (1.5.1 ~ 1.3.2 部分)
hash = (string)File["hash"];
category = "universal";
proi = 1;
}
break;
}
case "client":
{
if (proi <= 0 && File["format"].ToString() == "zip")
{
// 类型为 client.zip,支持范围 182~ (1.3.2 部分 ~)
hash = (string)File["hash"];
category = "client";
proi = 0;
}
break;
}
}
// 获取 Entry
var branch = (string)Token["branch"];
var name = (string)Token["version"];
// 基础信息获取
var entry = new DlForgeVersionEntry(name, branch, loader.input)
{ Hash = hash, Category = category, IsRecommended = (recommended ?? "") == (name ?? "") };
var timeSplit = Token["modified"].ToString().Split('-', 'T', ':', '.', ' ', '/');
entry.ReleaseTime = Lang.Date(Token["modified"].ToObject().ToLocalTime(), "g");
// 添加项
versions.Add(entry);
}
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Forge BMCLAPI", json),
ex);
}
if (!versions.Any())
throw new Exception(Lang.Text("Minecraft.Download.Error.NotFound"));
loader.output = versions;
}
#endregion
#region DlNeoForgeList | NeoForge 版本列表
public struct DlNeoForgeListResult
{
///
/// 数据来源名称,如“Official”,“BMCLAPI”。
///
public string sourceName;
///
/// 是否为官方的实时数据。
///
public bool isOfficial;
///
/// 所有版本的列表。已经按从新到老排序。
///
public List Value;
}
public class DlNeoForgeListEntry : DlForgelikeEntry
{
///
/// API 使用的原始版本字符串,如 “20.4.30-beta”、“1.20.1-47.1.99”(Legacy)。
///
public string ApiName;
///
/// 是否是 Beta 版。
///
public bool IsBeta;
public DlNeoForgeListEntry(string apiName)
{
forgeType = ForgelikeType.NeoForge;
this.ApiName = apiName;
IsBeta = apiName.Contains("beta") || apiName.Contains("alpha");
if (apiName.Contains("1.20.1")) // 1.20.1-47.1.99
{
VersionName = apiName.Replace("1.20.1-", "");
version = new Version("19." + VersionName);
Inherit = "1.20.1";
}
else if (apiName.StartsWith("0.")) // 0.25w14craftmine.3-beta
{
VersionName = apiName;
var segments = apiName.BeforeFirst("-").Split('.');
version = new Version(0, 0, int.Parse(segments.Last()));
Inherit = segments[1];
}
else // 20.4.30-beta;26.1.0.0-alpha.1+snapshot-1
{
VersionName = apiName;
version = new Version(apiName.BeforeFirst("-"));
if (version.Major >= 24)
Inherit = $"{version.Major}.{version.Minor}{(version.Build > 0 ? $".{version.Build}" : "")}";
else
Inherit = "1." + version.Major + (version.Minor > 0 ? "." + version.Minor : "");
if (VersionName.Contains("+"))
Inherit += "-" + VersionName.AfterFirst("+");
}
}
///
/// 文件在官网的基础地址,不包含后缀。
///
public string UrlBase
{
get
{
var packageName = IsLegacy ? "forge" : "neoforge";
return
$"https://maven.neoforged.net/releases/net/neoforged/{packageName}/{ApiName}/{packageName}-{ApiName}";
}
}
}
///
/// NeoForge 版本列表,主加载器。
///
public static ModLoader.LoaderTask dlNeoForgeListLoader =
new("DlNeoForgeList Main", DlNeoForgeListMain);
private static void DlNeoForgeListMain(ModLoader.LoaderTask loader)
{
switch (Config.Download.VersionListSource)
{
case 0:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlNeoForgeListBmclapiLoader, 30), new(dlNeoForgeListOfficialLoader, 30 + 60) },
loader.isForceRestarting);
break;
}
case 1:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlNeoForgeListOfficialLoader, 5), new(dlNeoForgeListBmclapiLoader, 5 + 30) },
loader.isForceRestarting);
break;
}
default:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlNeoForgeListOfficialLoader, 60), new(dlNeoForgeListBmclapiLoader, 60 + 60) },
loader.isForceRestarting);
break;
}
}
}
///
/// NeoForge 版本列表,官方源。
///
public static ModLoader.LoaderTask dlNeoForgeListOfficialLoader =
new("DlNeoForgeList Official", DlNeoForgeListOfficialMain);
private static void DlNeoForgeListOfficialMain(ModLoader.LoaderTask loader)
{
// 获取版本列表 JSON
var resultLatest = Requester.FetchJson(
"https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge",
new RequestParam
{
UseBrowserUserAgent = true
}).ToString();
var resultLegacy = Requester.FetchJson(
"https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge",
new RequestParam
{
UseBrowserUserAgent = true
}).ToString();
if (resultLatest.Length < 100 || resultLegacy.Length < 100)
throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "NeoForge",
resultLatest + "\r\n\r\n" + resultLegacy));
// 解析
try
{
loader.output = new DlNeoForgeListResult
{
isOfficial = true,
sourceName = Lang.Text("Download.Source.NeoForgeOfficial"),
Value = GetNeoForgeEntries(resultLatest, resultLegacy)
};
}
catch (Exception ex)
{
throw new Exception(
Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "NeoForge",
resultLatest + "\r\n\r\n" + resultLegacy), ex);
}
}
///
/// NeoForge 版本列表,BMCLAPI。
///
public static ModLoader.LoaderTask dlNeoForgeListBmclapiLoader =
new("DlNeoForgeList Bmclapi", DlNeoForgeListBmclapiMain);
public static void DlNeoForgeListBmclapiMain(ModLoader.LoaderTask loader)
{
// 获取版本列表 JSON
var resultLatest = Requester.FetchJson(
"https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge",
new RequestParam
{
UseBrowserUserAgent = true
}).ToString();
var resultLegacy = Requester.FetchJson(
"https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge",
new RequestParam
{
UseBrowserUserAgent = true
}).ToString();
if (resultLatest.Length < 100 || resultLegacy.Length < 100)
throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "NeoForge BMCLAPI",
resultLatest + "\r\n\r\n" + resultLegacy));
// 解析
try
{
loader.output = new DlNeoForgeListResult
{
isOfficial = true,
sourceName = "BMCLAPI",
Value = GetNeoForgeEntries(resultLatest, resultLegacy)
};
}
catch (Exception ex)
{
throw new Exception(
Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "NeoForge BMCLAPI",
resultLatest + "\r\n\r\n" + resultLegacy),
ex);
}
}
private static List GetNeoForgeEntries(string latestJson, string latestLegacyJson)
{
var versionNames = ModBase.RegexSearch(latestLegacyJson + latestJson, RegexPatterns.DlNeoForgeVersion);
var versions = versionNames.Where(name => name != "47.1.82").Select(name => new DlNeoForgeListEntry(name))
.OrderByDescending(a => a).ToList(); // 这个版本虽然在版本列表中,但不能下载
if (!versions.Any())
throw new Exception(Lang.Text("Minecraft.Download.Error.NotFound"));
return versions;
}
#endregion
#region DlCleanroomList | Cleanroom 版本列表
public struct DlCleanroomListResult
{
///
/// 数据来源名称,如“Official”,“BMCLAPI”。
///
public string sourceName;
///
/// 是否为官方的实时数据。
///
public bool isOfficial;
///
/// 所有版本的列表。已经按从新到老排序。
///
public List Value;
}
public class DlCleanroomListEntry : DlForgelikeEntry
{
///
/// API 使用的原始版本字符串,如 “0.2.4-alpha”。
///
public string ApiName;
///
/// 是否是 Beta 版。
///
public bool IsBeta;
public DlCleanroomListEntry(string apiName)
{
forgeType = ForgelikeType.Cleanroom;
this.ApiName = apiName;
IsBeta = apiName.Contains("alpha");
VersionName = apiName;
version = new Version(apiName.BeforeFirst("-"));
Inherit = "1.12.2";
}
///
/// 文件在官网的基础地址,不包含后缀。
///
public string UrlBase =>
$"https://github.com/CleanroomMC/Cleanroom/releases/download/{ApiName}/cleanroom-{ApiName}";
}
///
/// Cleanroom 版本列表,主加载器。
///
public static ModLoader.LoaderTask dlCleanroomListLoader =
new("DlCleanroomList Main", DlCleanroomListMain);
private static void DlCleanroomListMain(ModLoader.LoaderTask loader)
{
switch (Config.Download.VersionListSource)
{
case 0:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlCleanroomListOfficialLoader, 30) }, loader.isForceRestarting);
break;
}
case 1:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlCleanroomListOfficialLoader, 5) }, loader.isForceRestarting);
break;
}
default:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlCleanroomListOfficialLoader, 60) }, loader.isForceRestarting);
break;
}
}
}
///
/// Cleanroom 版本列表,官方源。
///
public static ModLoader.LoaderTask dlCleanroomListOfficialLoader =
new("DlCleanroomList Official", DlCleanroomListOfficialMain);
private static void DlCleanroomListOfficialMain(ModLoader.LoaderTask loader)
{
// 获取版本列表 JSON
var resultLatest = Requester.FetchJson(
"https://api.github.com/repos/CleanroomMC/Cleanroom/releases", new RequestParam
{
UseBrowserUserAgent = true
}).ToString();
if (resultLatest.Length < 100)
throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Cleanroom",
resultLatest));
// 解析
try
{
loader.output = new DlCleanroomListResult
{
isOfficial = true,
sourceName = Lang.Text("Download.Source.CleanroomOfficial"),
Value = GetCleanroomEntries(resultLatest)
};
}
catch (Exception ex)
{
throw new Exception(
Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Cleanroom", resultLatest), ex);
}
}
private static List GetCleanroomEntries(string latestJson)
{
var versions = new List();
var json = JsonArray.Parse(latestJson);
foreach (JsonObject Token in json.AsArray())
versions.Add(new DlCleanroomListEntry(Token["tag_name"].ToString())
{ forgeType = (DlForgelikeEntry.ForgelikeType)2 });
if (!versions.Any())
throw new Exception(Lang.Text("Minecraft.Download.Error.NoAvailableVersion"));
versions = versions.OrderByDescending(a => a.version).ToList();
return versions;
}
#endregion
#region DlLiteLoaderList | LiteLoader 版本列表
public struct DlLiteLoaderListResult
{
///
/// 数据来源名称,如“Official”,“BMCLAPI”。
///
public string sourceName;
///
/// 是否为官方的实时数据。
///
public bool isOfficial;
///
/// 获取到的数据。
///
public List Value;
///
/// 官方源的失败原因。若没有则为 Nothing。
///
public Exception officialError;
}
public class DlLiteLoaderListEntry
{
///
/// 实际的文件名,如“liteloader-installer-1.12-00-SNAPSHOT.jar”。
///
public string FileName;
///
/// 对应的 Minecraft 版本,如“1.12.2”。
///
public string Inherit;
///
/// 是否为 1.7 及更早的远古版。
///
public bool IsLegacy;
///
/// 是否为测试版。
///
public bool IsPreview;
///
/// 对应的 Json 项。
///
public JsonNode jsonToken;
///
/// 文件的 MD5。
///
public string MD5;
///
/// 发布时间,格式为“yyyy/mm/dd HH:mm”。
///
public string ReleaseTime;
}
///
/// LiteLoader 版本列表,主加载器。
///
public static ModLoader.LoaderTask dlLiteLoaderListLoader =
new("DlLiteLoaderList Main", DlLiteLoaderListMain);
private static void DlLiteLoaderListMain(ModLoader.LoaderTask loader)
{
switch (Config.Download.VersionListSource)
{
case 0:
{
DlSourceLoader(loader,
new List, int>>
{
new(dlLiteLoaderListBmclapiLoader, 30), new(dlLiteLoaderListOfficialLoader, 30 + 60)
}, loader.isForceRestarting);
break;
}
case 1:
{
DlSourceLoader(loader,
new List, int>>
{
new(dlLiteLoaderListOfficialLoader, 5), new(dlLiteLoaderListBmclapiLoader, 5 + 30)
}, loader.isForceRestarting);
break;
}
default:
{
DlSourceLoader(loader,
new List, int>>
{
new(dlLiteLoaderListOfficialLoader, 60), new(dlLiteLoaderListBmclapiLoader, 60 + 60)
}, loader.isForceRestarting);
break;
}
}
}
///
/// LiteLoader 版本列表,官方源。
///
public static ModLoader.LoaderTask dlLiteLoaderListOfficialLoader =
new("DlLiteLoaderList Official", DlLiteLoaderListOfficialMain);
private static void DlLiteLoaderListOfficialMain(ModLoader.LoaderTask loader)
{
var result =
(JsonObject)Requester.FetchJson("https://dl.liteloader.com/versions/versions.json");
try
{
var json = (JsonObject)result["versions"];
var versions = new List();
foreach (var Pair in json)
{
if (Pair.Key.StartsWithF("1.6") || Pair.Key.StartsWithF("1.5"))
continue;
var realEntry =
(Pair.Value["artefacts"] ?? Pair.Value["snapshots"])["com.mumfrey:liteloader"]["latest"];
versions.Add(new DlLiteLoaderListEntry
{
Inherit = Pair.Key,
IsLegacy = double.Parse(Pair.Key.Split(".")[1]) < 8d,
IsPreview = realEntry["stream"].ToString().ToLower() == "snapshot",
FileName = "liteloader-installer-" + Pair.Key +
(Pair.Key == "1.8" || Pair.Key == "1.9" ? ".0" : "") + "-00-SNAPSHOT.jar",
MD5 = (string)realEntry["md5"],
ReleaseTime = TimeUtils.FormatUnixTimestamp(long.Parse(realEntry["timestamp"].ToString())),
jsonToken = realEntry
});
}
loader.output = new DlLiteLoaderListResult
{ isOfficial = true, sourceName = Lang.Text("Download.Source.LiteLoaderOfficial"), Value = versions };
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "LiteLoader", result),
ex);
}
}
///
/// LiteLoader 版本列表,BMCLAPI。
///
public static ModLoader.LoaderTask dlLiteLoaderListBmclapiLoader =
new("DlLiteLoaderList Bmclapi", DlLiteLoaderListBmclapiMain);
private static void DlLiteLoaderListBmclapiMain(ModLoader.LoaderTask loader)
{
var result =
(JsonObject)Requester.FetchJson(
"https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json");
try
{
var json = (JsonObject)result["versions"];
var versions = new List();
foreach (var Pair in json)
{
if (Pair.Key.StartsWithF("1.6") || Pair.Key.StartsWithF("1.5"))
continue;
var realEntry =
(Pair.Value["artefacts"] ?? Pair.Value["snapshots"])["com.mumfrey:liteloader"]["latest"];
versions.Add(new DlLiteLoaderListEntry
{
Inherit = Pair.Key,
IsLegacy = double.Parse(Pair.Key.Split(".")[1]) < 8d,
IsPreview = realEntry["stream"].ToString().ToLower() == "snapshot",
FileName = "liteloader-installer-" + Pair.Key +
(Pair.Key == "1.8" || Pair.Key == "1.9" ? ".0" : "") + "-00-SNAPSHOT.jar",
MD5 = (string)realEntry["md5"],
ReleaseTime = TimeUtils.FormatUnixTimestamp(long.Parse((string)realEntry["timestamp"])),
jsonToken = realEntry
});
}
loader.output = new DlLiteLoaderListResult { isOfficial = false, sourceName = "BMCLAPI", Value = versions };
}
catch (Exception ex)
{
throw new Exception(
Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "LiteLoader BMCLAPI", result), ex);
}
}
#endregion
#region DlFabricList | Fabric 列表
public struct DlFabricListResult
{
///
/// 数据来源名称,如“Official”,“BMCLAPI”。
///
public string sourceName;
///
/// 是否为官方的实时数据。
///
public bool isOfficial;
///
/// 获取到的数据。
///
public JsonObject Value;
}
///
/// Fabric 列表,主加载器。
///
public static ModLoader.LoaderTask dlFabricListLoader =
new("DlFabricList Main", DlFabricListMain);
private static void DlFabricListMain(ModLoader.LoaderTask loader)
{
switch (Config.Download.VersionListSource)
{
case 0:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlFabricListBmclapiLoader, 30), new(dlFabricListOfficialLoader, 30 + 60) },
loader.isForceRestarting);
break;
}
case 1:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlFabricListOfficialLoader, 5), new(dlFabricListBmclapiLoader, 5 + 30) },
loader.isForceRestarting);
break;
}
default:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlFabricListOfficialLoader, 60), new(dlFabricListBmclapiLoader, 60 + 60) },
loader.isForceRestarting);
break;
}
}
}
///
/// Fabric 列表,官方源。
///
public static ModLoader.LoaderTask dlFabricListOfficialLoader =
new("DlFabricList Official", DlFabricListOfficialMain);
private static void DlFabricListOfficialMain(ModLoader.LoaderTask loader)
{
var result = (JsonObject)Requester.FetchJson("https://meta.fabricmc.net/v2/versions");
try
{
var output = new DlFabricListResult
{ isOfficial = true, sourceName = Lang.Text("Download.Source.FabricOfficial"), Value = result };
if (output.Value["game"] is null || output.Value["loader"] is null || output.Value["installer"] is null)
throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Fabric", result));
loader.output = output;
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Fabric", result), ex);
}
}
///
/// Fabric 列表,BMCLAPI。
///
public static ModLoader.LoaderTask dlFabricListBmclapiLoader =
new("DlFabricList Bmclapi", DlFabricListBmclapiMain);
private static void DlFabricListBmclapiMain(ModLoader.LoaderTask loader)
{
var result = (JsonObject)Requester.FetchJson("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions");
try
{
var output = new DlFabricListResult { isOfficial = false, sourceName = "BMCLAPI", Value = result };
if (output.Value["game"] is null || output.Value["loader"] is null || output.Value["installer"] is null)
throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Fabric BMCLAPI",
result));
loader.output = output;
}
catch (Exception ex)
{
throw new Exception(
Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "Fabric BMCLAPI", result), ex);
}
}
///
/// Fabric API 列表,官方源。
///
public static ModLoader.LoaderTask> dlFabricApiLoader = new("Fabric API List Loader",
task => task.output = ModComp.CompFilesGet("fabric-api", false));
///
/// OptiFabric 列表,官方源。
///
public static ModLoader.LoaderTask> dlOptiFabricLoader =
new("OptiFabric List Loader", task => task.output = ModComp.CompFilesGet("322385", true));
#endregion
#region DlLabyModList | LabyMod 列表
public struct DlLabyModListResult
{
///
/// 获取到的数据。
///
public JsonObject Value;
}
///
/// LabyMod 列表,主加载器。
///
public static ModLoader.LoaderTask dlLabyModListLoader =
new("DlLabyModList Main", DlLabyModListMain);
private static void DlLabyModListMain(ModLoader.LoaderTask loader)
{
switch (Config.Download.VersionListSource)
{
case 0:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlLabyModListOfficialLoader, 30), new(dlLabyModListOfficialLoader, 60) },
loader.isForceRestarting);
break;
}
case 1:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlLabyModListOfficialLoader, 5), new(dlLabyModListOfficialLoader, 35) },
loader.isForceRestarting);
break;
}
default:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlLabyModListOfficialLoader, 60), new(dlLabyModListOfficialLoader, 60) },
loader.isForceRestarting);
break;
}
}
}
///
/// LabyMod 列表,官方源。
///
public static ModLoader.LoaderTask dlLabyModListOfficialLoader =
new("DlLabyModList Official", DlLabyModListOfficialMain);
private static void DlLabyModListOfficialMain(ModLoader.LoaderTask loader)
{
JsonObject resultProduction;
using (var productionResponse = HttpRequest
.Create("https://releases.r2.labymod.net/api/v1/manifest/production/latest.json")
.WithHttpVersionOption(HttpVersion.Version20)
.SendAsync()
.GetAwaiter()
.GetResult())
{
resultProduction = (JsonObject)ModBase.GetJson(productionResponse.AsString());
}
JsonObject resultSnapshot;
using (var snapshotResponse = HttpRequest
.Create("https://releases.r2.labymod.net/api/v1/manifest/snapshot/latest.json")
.WithHttpVersionOption(HttpVersion.Version20)
.SendAsync()
.GetAwaiter()
.GetResult())
{
snapshotResponse.EnsureSuccessStatusCode();
resultSnapshot = (JsonObject)ModBase.GetJson(snapshotResponse.AsString());
}
var result = new JsonObject();
result.Add("production", resultProduction);
result.Add("snapshot", resultSnapshot);
try
{
var output = new DlLabyModListResult { Value = result };
if (output.Value["production"]["labyModVersion"] is null ||
output.Value["snapshot"]["labyModVersion"] is null)
throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "LabyMod",
result));
loader.output = output;
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "LabyMod", result),
ex);
}
}
#endregion
#region DlMod | Mod 镜像源请求
///
/// 对可能涉及 Mod 镜像源的请求进行处理,返回字符串。
/// 调用 NetGetCodeByRequest,会进行重试。
///
public static string DlModRequest(string url) => DlModRequest(url);
///
/// 对可能涉及 Mod 镜像源的请求进行处理,返回字符串或 JSON 对象。
/// 调用 NetGetCodeByRequest,会进行重试。
///
public static T DlModRequest(string url)
{
var urls = new List>();
var mcimUrl = DlSourceModGet(url);
if ((mcimUrl ?? "") != (url ?? ""))
switch (Config.Download.Comp.CompSourceSolution)
{
case 0:
{
urls.Add(new KeyValuePair(mcimUrl, 5));
urls.Add(new KeyValuePair(mcimUrl, 10));
urls.Add(new KeyValuePair(url, 15));
break;
}
case 1:
{
urls.Add(new KeyValuePair(url, 5));
urls.Add(new KeyValuePair(mcimUrl, 5));
urls.Add(new KeyValuePair(url, 15));
urls.Add(new KeyValuePair(mcimUrl, 10));
break;
}
default:
{
urls.Add(new KeyValuePair(url, 5));
urls.Add(new KeyValuePair(url, 15));
urls.Add(new KeyValuePair(mcimUrl, 10));
break;
}
}
var exs = "";
foreach (var Source in urls)
try
{
var json = Requester.FetchString(Source.Key, new RequestParam
{
Timeout = Source.Value * 1000,
UseBrowserUserAgent = true
});
if (typeof(T) == typeof(string)) return (T)(object)json;
return (T)(object)ModBase.GetJson(json);
}
catch (Exception ex)
{
// 镜像源可能随机爆炸,忽略就好
if (!ex.Message.ContainsF("mcimirror")) exs += ex.Message + "\r\n";
}
throw new Exception(exs);
}
///
/// 非泛型版本的 DlModRequest,返回 string
/// 对可能涉及 Mod 镜像源的请求进行处理。
/// 调用 NetRequest,会进行重试。
///
public static string DlModRequest(string url, string method, string data, string contentType,
bool allowMirror = false) => DlModRequest(url, method, data, contentType, allowMirror);
///
/// 对可能涉及 Mod 镜像源的请求进行处理。
/// 调用 NetRequest,会进行重试。
///
public static T DlModRequest(string url, string method, string data, string contentType,
bool allowMirror = false)
{
var urls = new List>();
var mcimUrl = DlSourceModGet(url);
if ((mcimUrl ?? "") != (url ?? ""))
switch (allowMirror ? Config.Download.Comp.CompSourceSolution : 2)
{
case 0:
{
urls.Add(new KeyValuePair(mcimUrl, 5));
urls.Add(new KeyValuePair(mcimUrl, 10));
urls.Add(new KeyValuePair(url, 15));
break;
}
case 1:
{
urls.Add(new KeyValuePair(url, 5));
urls.Add(new KeyValuePair(mcimUrl, 5));
urls.Add(new KeyValuePair(url, 15));
urls.Add(new KeyValuePair(mcimUrl, 10));
break;
}
default:
{
urls.Add(new KeyValuePair(url, 5));
urls.Add(new KeyValuePair(url, 15));
urls.Add(new KeyValuePair(mcimUrl, 10));
break;
}
}
var exs = "";
foreach (var Source in urls)
try
{
string json = Requester.Fetch(Source.Key, new FetchParam
{
Method = method,
Content = data,
ContentType = contentType,
Timeout = Source.Value * 1000
});
if (typeof(T) == typeof(string)) return (T)(object)json; // 沟槽的,为什么不能写 T is string
return (T)(object)ModBase.GetJson(json);
}
catch (Exception ex)
{
if (!ex.Message.ContainsF("mcimirror")) exs += ex.Message + "\r\n";
}
throw new Exception(exs);
}
#endregion
#region DlSource | 镜像下载源
private static bool dlPreferMojang;
///
/// 下载文件(而非获取版本列表)的时候,是否优先使用官方源。
///
public static bool DlSourcePreferMojang =>
Config.Download.FileSource == 2 ||
(Config.Download.FileSource == 1 && dlPreferMojang);
///
/// 下载文件(而非获取版本列表)的时候,根据是否优先使用官方源决定使用 Url 的顺序。
///
public static IEnumerable DlSourceOrder(IEnumerable officialUrls, IEnumerable mirrorUrls)
{
return DlSourcePreferMojang ? officialUrls.Union(mirrorUrls) : mirrorUrls.Union(officialUrls);
}
///
/// 获取版本列表(而非下载文件)的时候,是否优先使用官方源。
///
public static bool DlVersionListPreferMojang =>
Config.Download.VersionListSource == 2 ||
(Config.Download.VersionListSource == 1 && dlPreferMojang);
///
/// 获取版本列表(而非下载文件)的时候,根据是否优先使用官方源决定使用 Url 的顺序。
///
public static IEnumerable DlVersionListOrder(IEnumerable officialUrls,
IEnumerable mirrorUrls)
{
return DlVersionListPreferMojang ? officialUrls.Union(mirrorUrls) : mirrorUrls.Union(officialUrls);
}
///
/// 下载 Assets 文件。
///
public static IEnumerable DlSourceAssetsGet(string original)
{
original = original.Replace("http://resources.download.minecraft.net",
"https://resources.download.minecraft.net");
return DlSourceOrder(new[] { original },
new[]
{
original.Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/assets")
.Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/assets")
.Replace("https://resources.download.minecraft.net", "https://bmclapi2.bangbang93.com/assets")
});
}
///
/// 下载 Libraries 文件。
///
public static IEnumerable DlSourceLibraryGet(string original)
{
if (new[] { "minecraftforge", "fabricmc", "neoforged" }.Any(k => original.Contains(k))) // 不添加原版源
return new[]
{
original.Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/maven")
.Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/maven")
.Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/maven")
.Replace("https://zkitefly.github.io/unlisted-versions-of-minecraft",
"https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto"),
original.Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/libraries")
.Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/libraries")
.Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
.Replace("https://zkitefly.github.io/unlisted-versions-of-minecraft",
"https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto")
};
return DlSourceOrder(new[] { original },
new[]
{
original.Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/maven")
.Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/maven")
.Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/maven")
.Replace("https://zkitefly.github.io/unlisted-versions-of-minecraft",
"https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto"),
original.Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/libraries")
.Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/libraries")
.Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
.Replace("https://zkitefly.github.io/unlisted-versions-of-minecraft",
"https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto"),
original
});
}
///
/// 下载 Launcher 或 Meta 文件。
/// 不应使用它来获取版本列表(因为它只使用文件下载源设置来决定源顺序)。
///
public static IEnumerable DlSourceLauncherOrMetaGet(string original)
{
if (original is null)
throw new Exception(Lang.Text("Minecraft.Download.Error.NoJsonDownloadAddress"));
return DlSourceOrder(new[] { original },
new[]
{
original.Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com")
.Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com")
.Replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com")
.Replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com")
.Replace("https://zkitefly.github.io/unlisted-versions-of-minecraft",
"https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto"),
original
});
}
///
/// Mod Api 镜像源
///
///
///
public static string DlSourceModGet(string original)
{
return original.Replace("https://api.modrinth.com", "https://mod.mcimirror.top/modrinth")
.Replace("https://api.curseforge.com", "https://mod.mcimirror.top/curseforge");
}
///
/// Mod 下载镜像源
///
///
///
public static List DlSourceModDownloadGet(string original)
{
var res = new List();
var mirrorDl = original.Replace("https://cdn.modrinth.com", "https://mod.mcimirror.top")
.Replace("https://edge.forgecdn.net",
"https://mod.mcimirror.top"); // like https://cdn.modrinth.com/data/P7dR8mSH/versions/X2hTodix/fabric-api-0.129.0%2B1.21.8.jar
// like https://edge.forgecdn.net/files/6767/951/jei-1.21.5-neoforge-21.4.0.27.jar
switch (Config.Download.Comp.CompSourceSolution)
{
case 0: // 镜像源
{
res.Add(mirrorDl);
res.Add(mirrorDl);
break;
}
case 1: // 平衡
{
res.Add(original);
res.Add(mirrorDl);
break;
}
case 2: // 官方源
{
res.Add(original);
res.Add(original); // 错误
break;
}
default:
{
Config.Download.Comp.CompSourceSolution = 1;
res.Add(original);
break;
}
}
res.Add(original);
return res;
}
// Loader 自动切换
private static void DlSourceLoader(ModLoader.LoaderTask mainLoader,
List, int>> loaderList, bool isForceRestart = false)
{
var waitCycle = 0;
while (true)
{
// 检查状态
var beforeLoadersAllFailed = true;
foreach (var SubLoader in loaderList)
{
if (waitCycle == 0) // 判断是否可以不加载,直接使用已经加载好的结果
{
if (isForceRestart)
continue; // 强制刷新,不行
if (SubLoader.Key.input is null ^ mainLoader.input is null || (SubLoader.Key.input is not null &&
!SubLoader.Key.input.Equals(mainLoader.input)))
continue; // 父子加载器的输入不一样,也不行
}
if (SubLoader.Key.State != ModBase.LoadState.Failed)
beforeLoadersAllFailed = false;
if (SubLoader.Key.State == ModBase.LoadState.Finished)
{
// 检查加载器成功
mainLoader.output = SubLoader.Key.output;
DlSourceLoaderAbort(loaderList);
return;
}
if (beforeLoadersAllFailed)
// 此前的加载器全部失败,直接启动后续加载器
if (waitCycle < SubLoader.Value * 100)
waitCycle = SubLoader.Value * 100;
}
// 第一轮时:既然不直接使用已经加载好的结果,那就启动第一个加载器
if (waitCycle == 0)
{
loaderList.First().Key.Start(mainLoader.input, isForceRestart);
foreach (var Loader in loaderList.Skip(1))
Loader.Key.State = ModBase.LoadState.Waiting; // 将其他源标记为未启动,以确保可以切换下载源(#184)
}
// 检查加载器失败或超时
for (int i = 0, loopTo = loaderList.Count - 1; i <= loopTo; i++)
{
if (waitCycle != loaderList[i].Value * 100)
continue;
if (i < loaderList.Count - 1 && !loaderList.All(l => l.Key.State == ModBase.LoadState.Failed))
{
// 若还有下一个源,则启动下一个源
loaderList[i + 1].Key.Start(mainLoader.input, isForceRestart);
}
else
{
// 若没有,则失败
Exception errorInfo = null;
for (int ii = 0, loopTo1 = loaderList.Count - 1; ii <= loopTo1; ii++)
{
loaderList[ii].Key.input = default; // 重置输入,以免以同样的输入“重试加载”时直接失败
if (loaderList[ii].Key.Error is null) continue;
if (errorInfo is null || loaderList[ii].Key.Error.Message
.Contains(Lang.Text("Minecraft.Download.Error.NotFound")))
errorInfo = loaderList[ii].Key.Error;
}
errorInfo ??= new TimeoutException(Lang.Text("Minecraft.Download.Error.Timeout"));
DlSourceLoaderAbort(loaderList);
throw errorInfo;
}
break;
}
// 计时
Thread.Sleep(10);
waitCycle += 1;
// 检查父加载器中断
if (mainLoader.IsAborted)
{
DlSourceLoaderAbort(loaderList);
return;
}
}
}
private static void DlSourceLoaderAbort(
List, int>> loaderList)
{
foreach (var Loader in loaderList)
if (Loader.Key.State == ModBase.LoadState.Loading)
Loader.Key.Abort();
}
#endregion
#region DlLegacyFabricList | LegacyFabric 列表
public struct DlLegacyFabricListResult
{
///
/// 数据来源名称,如“Official”,“BMCLAPI”。
///
public string sourceName;
///
/// 是否为官方的实时数据。
///
public bool isOfficial;
///
/// 获取到的数据。
///
public JsonObject Value;
}
///
/// LegacyFabric 列表,主加载器。
///
public static ModLoader.LoaderTask dlLegacyFabricListLoader =
new("DlLegacyFabricList Main", DlLegacyFabricListMain);
private static void DlLegacyFabricListMain(ModLoader.LoaderTask loader)
{
switch (Config.Download.VersionListSource)
{
case 0:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlLegacyFabricListOfficialLoader, 30) }, loader.isForceRestarting);
break;
}
case 1:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlLegacyFabricListOfficialLoader, 5) }, loader.isForceRestarting);
break;
}
default:
{
DlSourceLoader(loader,
new List, int>>
{ new(dlLegacyFabricListOfficialLoader, 60) }, loader.isForceRestarting);
break;
}
}
}
///
/// LegacyFabric 列表,官方源。
///
public static ModLoader.LoaderTask dlLegacyFabricListOfficialLoader =
new("DlLegacyFabricList Official", DlLegacyFabricListOfficialMain);
private static void DlLegacyFabricListOfficialMain(ModLoader.LoaderTask loader)
{
var result =
(JsonObject)Requester.FetchJson("https://meta.legacyfabric.net/v2/versions");
try
{
var output = new DlLegacyFabricListResult
{ isOfficial = true, sourceName = Lang.Text("Download.Source.LegacyFabricOfficial"), Value = result };
if (output.Value["game"] is null || output.Value["loader"] is null || output.Value["installer"] is null)
throw new Exception(Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "LegacyFabric",
result));
loader.output = output;
}
catch (Exception ex)
{
throw new Exception(
Lang.Text("Minecraft.Download.Error.VersionListOperationFailed", "LegacyFabric", result), ex);
}
}
///
/// Legacy Fabric API 列表,官方源。
///
public static ModLoader.LoaderTask> dlLegacyFabricApiLoader =
new("Legacy Fabric API List Loader", task => task.output = ModComp.CompFilesGet("legacy-fabric-api", false));
#endregion
///
/// 发送 Minecraft 更新提示。
///
public static void McDownloadClientUpdateHint(string versionName, JsonObject json)
{
try
{
// 获取对应版本
JsonNode version = null;
foreach (var Token in json["versions"].AsArray())
if (Token["id"] is not null && (Token["id"].ToString() ?? "") == (versionName ?? ""))
{
version = Token;
break;
}
// 进行提示
if (version is null)
return;
var time = version["releaseTime"].ToObject();
var msgBoxText = Lang.Text("Minecraft.Update.NewVersion", versionName) + "\r\n" +
((DateTime.Now - time).TotalDays > 1d
? Lang.Text("Minecraft.Update.UpdateTime") + Lang.Date(time)
: Lang.Text("Minecraft.Update.UpdatedAt") + Lang.TimeSpan(time - DateTime.Now));
var msgResult = ModMain.MyMsgBox(msgBoxText, Lang.Text("Minecraft.Update.Title"),
Lang.Text("Common.Action.Confirm"), Lang.Text("Common.Action.Download"),
(DateTime.Now - time).TotalHours > 3d ? Lang.Text("Common.Action.UpdateLog") : "",
button3Action: () => ModDownloadLib.McUpdateLogShow(version));
// 弹窗结果
if (msgResult == 2)
// 下载
ModBase.RunInUi(() =>
{
PageDownloadInstall.mcVersionWaitingForSelect = versionName;
ModMain.frmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadInstall);
});
}
catch (Exception ex)
{
ModBase.Log(
ex,
Lang.Text("Minecraft.Error.UpdateNotify", versionName ?? "Nothing"),
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Minecraft.Error.UpdateNotify", versionName ?? "Nothing"));
}
}
}
|