692 lines
32 KiB
C#
692 lines
32 KiB
C#
using System;
|
||||
|
|
using System.IO;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Text.Json.Nodes;
|
|||
|
|
using PCL.Core.App;
|
|||
|
|
using PCL.Core.Utils;
|
|||
|
|
using PCL.Core.Utils.Exts;
|
|||
|
|
using PCL.Core.Utils.OS;
|
|||
|
|
using PCL.Network;
|
|||
|
|
|
|||
|
|
namespace PCL;
|
|||
|
|
|
|||
|
|
public static class ModLibrary
|
|||
|
|
{
|
|||
|
|
public class McLibToken
|
|||
|
|
{
|
|||
|
|
private string _Url;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 是否为纯本地文件,若是则不尝试联网下载。
|
|||
|
|
/// </summary>
|
|||
|
|
public bool IsLocal;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 是否为 Natives 文件。
|
|||
|
|
/// </summary>
|
|||
|
|
public bool IsNatives;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 文件的完整本地路径。
|
|||
|
|
/// </summary>
|
|||
|
|
public string LocalPath;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 原 JSON 中的 Name 项。
|
|||
|
|
/// </summary>
|
|||
|
|
public string OriginalName;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 文件的 SHA1。
|
|||
|
|
/// </summary>
|
|||
|
|
public string Sha1;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 文件大小。若无有效数据即为 0。
|
|||
|
|
/// </summary>
|
|||
|
|
public long size;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 由 JSON 提供的 URL,若没有则为 Nothing。
|
|||
|
|
/// </summary>
|
|||
|
|
public string Url
|
|||
|
|
{
|
|||
|
|
get => _Url;
|
|||
|
|
set =>
|
|||
|
|
// 孤儿 Forge 作者喜欢把没有 URL 的写个空字符串
|
|||
|
|
_Url = string.IsNullOrWhiteSpace(value) ? null : value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 原 JSON 中 Name 项除去版本号部分的较前部分。可能为 Nothing。
|
|||
|
|
/// </summary>
|
|||
|
|
public string Name
|
|||
|
|
{
|
|||
|
|
get
|
|||
|
|
{
|
|||
|
|
if (OriginalName is null)
|
|||
|
|
return null;
|
|||
|
|
var splited = new List<string>(OriginalName.Split(":"));
|
|||
|
|
splited.RemoveAt(2); // Java 的此格式下版本号固定为第三段,第四段可能包含架构、分包等其他信息
|
|||
|
|
return splited.Join(":");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return (IsNatives ? "[Native] " : "") + ModBase.GetString(size) + " | " + LocalPath;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 检查是否符合 JSON 中的 Rules。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="ruleToken">JSON 中的 "rules" 项目。</param>
|
|||
|
|
public static bool McJsonRuleCheck(JsonNode ruleToken)
|
|||
|
|
{
|
|||
|
|
if (ruleToken is null)
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
// 初始化
|
|||
|
|
var required = false;
|
|||
|
|
foreach (var Rule in ruleToken.AsArray())
|
|||
|
|
{
|
|||
|
|
// 单条条件验证
|
|||
|
|
var isRightRule = true; // 是否为正确的规则
|
|||
|
|
if (Rule["os"] is not null) // 操作系统
|
|||
|
|
{
|
|||
|
|
if (Rule["os"]["name"] is not null) // 操作系统名称
|
|||
|
|
{
|
|||
|
|
var osName = Rule["os"]["name"].ToString();
|
|||
|
|
if (osName == "unknown")
|
|||
|
|
{
|
|||
|
|
}
|
|||
|
|
else if (osName == "windows")
|
|||
|
|
{
|
|||
|
|
if (Rule["os"]["version"] is not null) // 操作系统版本
|
|||
|
|
{
|
|||
|
|
var cr = Rule["os"]["version"].ToString();
|
|||
|
|
isRightRule = isRightRule && osVersion.RegexCheck(cr);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
isRightRule = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Rule["os"]["arch"] is not null) // 操作系统架构
|
|||
|
|
isRightRule = isRightRule && Rule["os"]["arch"].ToString() == "x86" == SystemInfo.Is32BitSystem;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Rule["features"] is not null) // 标签
|
|||
|
|
{
|
|||
|
|
isRightRule = isRightRule && Rule["features"]["is_demo_user"] is null; // 反选是否为 Demo 用户
|
|||
|
|
if (Rule["features"].AsObject().Any(prop => prop.Key.Contains("quick_play")))
|
|||
|
|
isRightRule = false; // 不开 Quick Play,让玩家自己加去
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 反选确认
|
|||
|
|
if (Rule["action"].ToString() == "allow")
|
|||
|
|
{
|
|||
|
|
if (isRightRule)
|
|||
|
|
required = true; // allow
|
|||
|
|
}
|
|||
|
|
else if (isRightRule)
|
|||
|
|
{
|
|||
|
|
required = false; // disallow
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return required;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static readonly string osVersion = Environment.OSVersion.Version.ToString();
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 递归获取 Minecraft 某一实例的完整支持库列表。
|
|||
|
|
/// </summary>
|
|||
|
|
public static List<McLibToken> McLibListGet(McInstance mcInstance, bool includeInstanceJar)
|
|||
|
|
{
|
|||
|
|
// 获取当前支持库列表
|
|||
|
|
ModBase.Log("[Minecraft] 获取支持库列表:" + mcInstance.Name);
|
|||
|
|
var result = McLibListGetWithJson(mcInstance.JsonObject, targetMcInstance: mcInstance);
|
|||
|
|
|
|||
|
|
// 需要添加原版 Jar
|
|||
|
|
if (includeInstanceJar)
|
|||
|
|
{
|
|||
|
|
McInstance realMcInstance;
|
|||
|
|
var requiredJar = mcInstance.JsonObject["jar"]?.ToString();
|
|||
|
|
if (mcInstance.IsHmclFormatJson || requiredJar is null)
|
|||
|
|
{
|
|||
|
|
// HMCL 项直接使用自身的 Jar
|
|||
|
|
// 根据 Inherit 获取最深层实例
|
|||
|
|
var originalInstance = mcInstance;
|
|||
|
|
// 1.17+ 的 Forge 不寻找 Inherit
|
|||
|
|
if (!((mcInstance.Info.HasForge || mcInstance.Info.HasNeoForge) && mcInstance.Info.Drop >= 170))
|
|||
|
|
while (!string.IsNullOrEmpty(originalInstance.InheritInstanceName))
|
|||
|
|
{
|
|||
|
|
if ((originalInstance.InheritInstanceName ?? "") == (originalInstance.Name ?? ""))
|
|||
|
|
break;
|
|||
|
|
originalInstance = new McInstance(Path.Combine(ModFolder.mcFolderSelected, "versions", originalInstance.InheritInstanceName));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 需要新建对象,否则后面的 Check 会导致 McInstanceCurrent 的 State 变回 Original
|
|||
|
|
// 复现:启动一个 Snapshot 实例
|
|||
|
|
realMcInstance = new McInstance(originalInstance.PathInstance);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// Json 已提供 Jar 字段,使用该字段的信息
|
|||
|
|
realMcInstance = new McInstance(requiredJar);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
string clientUrl;
|
|||
|
|
string clientSHA1;
|
|||
|
|
// 判断需求的实例是否存在
|
|||
|
|
// 不能调用 RealVersion.Check(),可能会莫名其妙地触发 CheckPermission 正被另一进程使用,导致误判前置不存在
|
|||
|
|
if (!File.Exists(realMcInstance.PathInstance + realMcInstance.Name + ".json"))
|
|||
|
|
{
|
|||
|
|
realMcInstance = mcInstance;
|
|||
|
|
ModBase.Log("[Minecraft] 可能缺少前置实例 " + realMcInstance.Name + ",找不到对应的 JSON 文件", ModBase.LogLevel.Debug);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取详细下载信息
|
|||
|
|
if (realMcInstance.JsonObject["downloads"] is not null &&
|
|||
|
|
realMcInstance.JsonObject["downloads"]["client"] is not null)
|
|||
|
|
{
|
|||
|
|
clientUrl = (string)realMcInstance.JsonObject["downloads"]["client"]["url"];
|
|||
|
|
clientSHA1 = (string)realMcInstance.JsonObject["downloads"]["client"]["sha1"];
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
clientUrl = null;
|
|||
|
|
clientSHA1 = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 把所需的原版 Jar 添加进去
|
|||
|
|
result.Add(new McLibToken
|
|||
|
|
{
|
|||
|
|
LocalPath = realMcInstance.PathInstance + realMcInstance.Name + ".jar", size = 0L, IsNatives = false,
|
|||
|
|
Url = clientUrl, Sha1 = clientSHA1
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取 Minecraft 某一实例忽视继承的支持库列表,即结果中没有继承项。
|
|||
|
|
/// </summary>
|
|||
|
|
public static List<McLibToken> McLibListGetWithJson(JsonObject jsonObject,
|
|||
|
|
bool keepSameNameDifferentVersionResult = false, string customMcFolder = null, McInstance targetMcInstance = null)
|
|||
|
|
{
|
|||
|
|
customMcFolder = customMcFolder ?? ModFolder.mcFolderSelected;
|
|||
|
|
var basicArray = new List<McLibToken>();
|
|||
|
|
|
|||
|
|
// 添加基础 Json 项
|
|||
|
|
var allLibs = (JsonArray)jsonObject["libraries"];
|
|||
|
|
|
|||
|
|
// 转换为 LibToken
|
|||
|
|
foreach (var LibraryNode in allLibs)
|
|||
|
|
{
|
|||
|
|
var library = LibraryNode.AsObject();
|
|||
|
|
// 清理 null 项(BakaXL 会把没有的项序列化为 null;这导致了 #409)
|
|||
|
|
var keysToRemove = library.Where(p => p.Value?.GetValueKind() == JsonValueKind.Null).Select(p => p.Key).ToList();
|
|||
|
|
foreach (var key in keysToRemove)
|
|||
|
|
library.Remove(key);
|
|||
|
|
|
|||
|
|
// 检查是否需要(Rules)
|
|||
|
|
if (!McJsonRuleCheck(library["rules"]))
|
|||
|
|
continue;
|
|||
|
|
|
|||
|
|
// 获取根节点下的 url
|
|||
|
|
var rootUrl = (string)library["url"];
|
|||
|
|
if (rootUrl is not null)
|
|||
|
|
rootUrl += McLibGet((string)library["name"], false, true, customMcFolder).Replace(@"\", "/");
|
|||
|
|
|
|||
|
|
// 是否为纯本地项
|
|||
|
|
var hint = (string)library["hint"];
|
|||
|
|
var isLocal = hint is not null ? hint == "local" : false;
|
|||
|
|
|
|||
|
|
// 根据是否本地化处理(Natives)
|
|||
|
|
if (library["natives"] is null) // 没有 Natives
|
|||
|
|
{
|
|||
|
|
string localPath;
|
|||
|
|
if (isLocal && targetMcInstance is not null) // 纯本地项
|
|||
|
|
localPath = targetMcInstance.PathInstance + @"libraries\" +
|
|||
|
|
library["name"].ToString().AfterFirst(":").Replace(":", "-") + ".jar";
|
|||
|
|
else
|
|||
|
|
localPath = McLibGet((string)library["name"], customMcFolder: customMcFolder);
|
|||
|
|
var artifactPath = library["downloads"] is not null && library["downloads"]["artifact"] is not null
|
|||
|
|
? library["downloads"]["artifact"]["path"]
|
|||
|
|
: null;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (library["downloads"] is not null && library["downloads"]["artifact"] is not null)
|
|||
|
|
{
|
|||
|
|
var init = new McLibToken();
|
|||
|
|
basicArray.Add((init.OriginalName = (string)library["name"],
|
|||
|
|
init.Url = (string)(rootUrl ?? library["downloads"]["artifact"]["url"]),
|
|||
|
|
init.LocalPath = artifactPath is null
|
|||
|
|
? McLibGet((string)library["name"], customMcFolder: customMcFolder)
|
|||
|
|
: McLibGetByArtifactPath(artifactPath.ToString(), customMcFolder),
|
|||
|
|
init.size = (long)Math.Round(
|
|||
|
|
ModBase.Val(library["downloads"]["artifact"]["size"].ToString())),
|
|||
|
|
init.IsNatives = false, init.Sha1 = library["downloads"]["artifact"]["sha1"]?.ToString(),
|
|||
|
|
init.IsLocal = isLocal, init).init);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
basicArray.Add(new McLibToken
|
|||
|
|
{
|
|||
|
|
OriginalName = (string)library["name"], Url = rootUrl, LocalPath = localPath, size = 0L,
|
|||
|
|
IsNatives = false, Sha1 = null, IsLocal = isLocal
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex) when (artifactPath is not null && (ex is ArgumentException || ex is IOException))
|
|||
|
|
{
|
|||
|
|
ModBase.Log(ex, "支持库下载路径非法,已跳过(无 Natives," + (library["name"] ?? "Nothing") + ")");
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
ModBase.Log(ex, "处理实际支持库列表失败(无 Natives," + (library["name"] ?? "Nothing") + ")");
|
|||
|
|
basicArray.Add(new McLibToken
|
|||
|
|
{
|
|||
|
|
OriginalName = (string)library["name"], Url = rootUrl, LocalPath = localPath, size = 0L,
|
|||
|
|
IsNatives = false, Sha1 = null
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (library["natives"]["windows"] is not null) // 有 Windows Natives
|
|||
|
|
{
|
|||
|
|
var nativePath = library["downloads"] is not null && library["downloads"]["classifiers"] is not null &&
|
|||
|
|
library["downloads"]["classifiers"]["natives-windows"] is not null
|
|||
|
|
? library["downloads"]["classifiers"]["natives-windows"]["path"]
|
|||
|
|
: null;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (library["downloads"] is not null && library["downloads"]["classifiers"] is not null &&
|
|||
|
|
library["downloads"]["classifiers"]["natives-windows"] is not null)
|
|||
|
|
basicArray.Add(new McLibToken
|
|||
|
|
{
|
|||
|
|
OriginalName = (string)library["name"],
|
|||
|
|
Url = (string)(rootUrl ?? library["downloads"]["classifiers"]["natives-windows"]["url"]),
|
|||
|
|
LocalPath = nativePath is null
|
|||
|
|
? McLibGet((string)library["name"], customMcFolder: customMcFolder)
|
|||
|
|
.Replace(".jar", "-" + library["natives"]["windows"] + ".jar")
|
|||
|
|
.Replace("${arch}", Environment.Is64BitOperatingSystem ? "64" : "32")
|
|||
|
|
: McLibGetByArtifactPath(nativePath.ToString(), customMcFolder),
|
|||
|
|
size = (long)Math.Round(
|
|||
|
|
ModBase.Val(library["downloads"]["classifiers"]["natives-windows"]["size"].ToString())),
|
|||
|
|
IsNatives = true,
|
|||
|
|
Sha1 = library["downloads"]["classifiers"]["natives-windows"]["sha1"].ToString(),
|
|||
|
|
IsLocal = isLocal
|
|||
|
|
});
|
|||
|
|
else
|
|||
|
|
basicArray.Add(new McLibToken
|
|||
|
|
{
|
|||
|
|
OriginalName = (string)library["name"], Url = rootUrl,
|
|||
|
|
LocalPath = McLibGet((string)library["name"], customMcFolder: customMcFolder)
|
|||
|
|
.Replace(".jar", "-" + library["natives"]["windows"] + ".jar")
|
|||
|
|
.Replace("${arch}", Environment.Is64BitOperatingSystem ? "64" : "32"),
|
|||
|
|
size = 0L, IsNatives = true, Sha1 = null, IsLocal = isLocal
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
catch (Exception ex) when (nativePath is not null && (ex is ArgumentException || ex is IOException))
|
|||
|
|
{
|
|||
|
|
ModBase.Log(ex, "支持库下载路径非法,已跳过(有 Natives," + (library["name"] ?? "Nothing") + ")");
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
ModBase.Log(ex, "处理实际支持库列表失败(有 Natives," + (library["name"] ?? "Nothing") + ")");
|
|||
|
|
basicArray.Add(new McLibToken
|
|||
|
|
{
|
|||
|
|
OriginalName = (string)library["name"], Url = rootUrl,
|
|||
|
|
LocalPath = McLibGet((string)library["name"], customMcFolder: customMcFolder)
|
|||
|
|
.Replace(".jar", "-" + library["natives"]["windows"] + ".jar")
|
|||
|
|
.Replace("${arch}", Environment.Is64BitOperatingSystem ? "64" : "32"),
|
|||
|
|
size = 0L, IsNatives = true, Sha1 = null, IsLocal = false
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 去重
|
|||
|
|
var resultArray = new Dictionary<string, McLibToken>();
|
|||
|
|
|
|||
|
|
// 测试例:
|
|||
|
|
// D:\Minecraft\test\libraries\net\neoforged\mergetool\2.0.0\mergetool-2.0.0-api.jar
|
|||
|
|
// D:\Minecraft\test\libraries\org\apache\commons\commons-collections4\4.2\commons-collections4-4.2.jar
|
|||
|
|
// D:\Minecraft\test\libraries\com\google\guava\guava\31.1-jre\guava-31.1-jre.jar
|
|||
|
|
string GetVersion(McLibToken token)
|
|||
|
|
{
|
|||
|
|
return ModBase.GetFolderNameFromPath(ModBase.GetPathFromFullPath(token.LocalPath));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (int i = 0, loopTo = basicArray.Count - 1; i <= loopTo; i++)
|
|||
|
|
{
|
|||
|
|
var key = basicArray[i].Name + basicArray[i].IsNatives;
|
|||
|
|
if (resultArray.ContainsKey(key))
|
|||
|
|
{
|
|||
|
|
var basicArrayVersion = GetVersion(basicArray[i]);
|
|||
|
|
var resultArrayVersion = GetVersion(resultArray[key]);
|
|||
|
|
if ((basicArrayVersion ?? "") != (resultArrayVersion ?? "") && keepSameNameDifferentVersionResult)
|
|||
|
|
{
|
|||
|
|
ModBase.Log(
|
|||
|
|
$"[Minecraft] 发现疑似重复的支持库:{basicArray[i]} ({basicArrayVersion}) 与 {resultArray[key]} ({resultArrayVersion})");
|
|||
|
|
resultArray.Add(key + ModBase.GetUuid(), basicArray[i]);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
ModBase.Log(
|
|||
|
|
$"[Minecraft] 发现重复的支持库:{basicArray[i]} ({basicArrayVersion}) 与 {resultArray[key]} ({resultArrayVersion}),已忽略其中之一");
|
|||
|
|
if (McVersionComparer.CompareVersionGe(basicArrayVersion, resultArrayVersion)) resultArray[key] = basicArray[i];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
resultArray.Add(key, basicArray[i]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return resultArray.Values.ToList();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取实例所需支持库文件的 NetFile。
|
|||
|
|
/// </summary>
|
|||
|
|
public static List<DownloadFile> McLibNetFilesFromInstance(McInstance mcInstance)
|
|||
|
|
{
|
|||
|
|
if (!mcInstance.IsLoaded)
|
|||
|
|
mcInstance.Load();
|
|||
|
|
var result = new List<DownloadFile>();
|
|||
|
|
|
|||
|
|
// 更新此方法时需要同步更新 Forge 新版自动安装方法!
|
|||
|
|
|
|||
|
|
// 主 Jar 文件
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var mainJar = ModDownload.DlClientJarGet(mcInstance, true);
|
|||
|
|
if (mainJar is not null)
|
|||
|
|
result.Add(mainJar);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
ModBase.Log(ex, "实例缺失主 Jar 文件所必须的信息", ModBase.LogLevel.Developer);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Library 文件
|
|||
|
|
result.AddRange(McLibNetFilesFromTokens(McLibListGet(mcInstance, false)));
|
|||
|
|
|
|||
|
|
// Authlib-Injector 文件
|
|||
|
|
var authlibTargetFile = Path.Combine(ModBase.pathPure, "authlib-injector.jar");
|
|||
|
|
JsonObject authlibDownloadInfo = null;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
ModBase.Log("[Minecraft] 开始获取 Authlib-Injector 下载信息");
|
|||
|
|
authlibDownloadInfo = (JsonObject)ModBase.GetJson(ModNet.NetGetCodeByLoader(
|
|||
|
|
new[]
|
|||
|
|
{
|
|||
|
|
"https://authlib-injector.yushi.moe/artifact/latest.json",
|
|||
|
|
"https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json"
|
|||
|
|
}, isJson: true));
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
ModBase.Log(ex, "获取 Authlib-Injector 下载信息失败");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 校验文件
|
|||
|
|
if (authlibDownloadInfo is not null)
|
|||
|
|
{
|
|||
|
|
var checker = new ModBase.FileChecker(hash: authlibDownloadInfo["checksums"]["sha256"].ToString());
|
|||
|
|
if (checker.Check(authlibTargetFile) is not null)
|
|||
|
|
{
|
|||
|
|
// 开始下载
|
|||
|
|
var downloadAddress = authlibDownloadInfo["download_url"].ToString()
|
|||
|
|
.Replace("bmclapi2.bangbang93.com/mirrors/authlib-injector", "authlib-injector.yushi.moe");
|
|||
|
|
ModBase.Log("[Minecraft] Authlib-Injector 需要更新:" + downloadAddress, ModBase.LogLevel.Developer);
|
|||
|
|
result.Add(new DownloadFile(
|
|||
|
|
new[]
|
|||
|
|
{
|
|||
|
|
downloadAddress,
|
|||
|
|
downloadAddress.Replace("authlib-injector.yushi.moe",
|
|||
|
|
"bmclapi2.bangbang93.com/mirrors/authlib-injector")
|
|||
|
|
}, authlibTargetFile,
|
|||
|
|
new ModBase.FileChecker(hash: authlibDownloadInfo["checksums"]["sha256"].ToString())));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 修改渲染器
|
|||
|
|
var mesaLoaderWindowsTargetFile =
|
|||
|
|
Path.Combine(ModBase.pathPure, "mesa-loader-windows", ModLaunch.mesaLoaderWindowsVersion, "Loader.jar");
|
|||
|
|
var renderer = -1;
|
|||
|
|
if (ModInstanceList.McMcInstanceSelected is not null)
|
|||
|
|
renderer = Config.Instance.Renderer[ModInstanceList.McMcInstanceSelected?.PathInstance] - 1;
|
|||
|
|
if (renderer == -1) renderer = Config.Launch.Renderer;
|
|||
|
|
|
|||
|
|
if (renderer != 0 && !File.Exists(mesaLoaderWindowsTargetFile))
|
|||
|
|
{
|
|||
|
|
var downloadAddress =
|
|||
|
|
"https://mirrors.cloud.tencent.com/nexus/repository/maven-public/org/glavo/mesa-loader-windows/" +
|
|||
|
|
ModLaunch.mesaLoaderWindowsVersion + "/mesa-loader-windows-" + ModLaunch.mesaLoaderWindowsVersion + "-" +
|
|||
|
|
(SystemInfo.Is32BitSystem ? "x86" : SystemInfo.IsArm64System ? "arm64" : "x64") + ".jar";
|
|||
|
|
result.Add(new DownloadFile(new[] { downloadAddress }, mesaLoaderWindowsTargetFile));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// LabyMod Assets 文件
|
|||
|
|
if (mcInstance.Info.HasLabyMod)
|
|||
|
|
{
|
|||
|
|
if ((mcInstance.PathIndie ?? "") == (mcInstance.PathInstance ?? ""))
|
|||
|
|
{
|
|||
|
|
if (Directory.Exists(Path.Combine(mcInstance.PathInstance, "labymod-neo")))
|
|||
|
|
Directory.Delete(Path.Combine(mcInstance.PathInstance, "labymod-neo"), true);
|
|||
|
|
ModBase.CreateSymbolicLink(Path.Combine(mcInstance.PathInstance, "labymod-neo"), Path.Combine(ModFolder.mcFolderSelected, "labymod-neo"),
|
|||
|
|
0x2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var channelType = mcInstance.JsonObject["labymod_data"]["channelType"].ToString();
|
|||
|
|
Directory.CreateDirectory($@"{ModFolder.mcFolderSelected}labymod-neo\libraries");
|
|||
|
|
ModBase.Log("[Minecraft] 开始获取 LabyMod 信息");
|
|||
|
|
var labyManifest = (JsonObject)ModNet.NetGetCodeByRequestRetry(
|
|||
|
|
$"https://releases.r2.labymod.net/api/v1/manifest/{channelType}/latest.json", isJson: true);
|
|||
|
|
var labyAssets = (JsonObject)labyManifest["assets"];
|
|||
|
|
var labyModCommitRef = labyManifest["commitReference"].ToString();
|
|||
|
|
foreach (var Asset in labyAssets)
|
|||
|
|
{
|
|||
|
|
var assetName = Asset.Key;
|
|||
|
|
var assetSHA1 = Asset.Value.ToString();
|
|||
|
|
var assetPath = $@"{ModFolder.mcFolderSelected}labymod-neo\assets\{assetName}.jar";
|
|||
|
|
var assetUrl =
|
|||
|
|
$"https://releases.r2.labymod.net/api/v1/download/assets/labymod4/{channelType}/{labyModCommitRef}/{assetName}/{assetSHA1}.jar";
|
|||
|
|
var checker = new ModBase.FileChecker(hash: assetSHA1);
|
|||
|
|
if (checker.Check(assetPath) is null)
|
|||
|
|
continue;
|
|||
|
|
result.Add(new DownloadFile(new[] { assetUrl }, assetPath, checker));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
ModBase.Log(ex, "获取 LabyMod 信息失败,跳过检查");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 跳过校验
|
|||
|
|
if (ShouldIgnoreFileCheck(mcInstance))
|
|||
|
|
{
|
|||
|
|
ModBase.Log("[Minecraft] 用户要求尽量忽略文件检查,这可能会保留有误的文件");
|
|||
|
|
result = result.Where(f =>
|
|||
|
|
{
|
|||
|
|
if (File.Exists(f.LocalPath))
|
|||
|
|
{
|
|||
|
|
ModBase.Log("[Minecraft] 跳过下载的支持库文件:" + f.LocalPath, ModBase.LogLevel.Debug);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}).ToList();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 将 McLibToken 列表转换为 NetFile。
|
|||
|
|
/// </summary>
|
|||
|
|
public static List<DownloadFile> McLibNetFilesFromTokens(List<McLibToken> libs, string customMcFolder = null)
|
|||
|
|
{
|
|||
|
|
customMcFolder = customMcFolder ?? ModFolder.mcFolderSelected;
|
|||
|
|
var result = new List<DownloadFile>();
|
|||
|
|
// 获取
|
|||
|
|
foreach (var token in libs)
|
|||
|
|
{
|
|||
|
|
// 检查文件
|
|||
|
|
var checker = new ModBase.FileChecker(actualSize: token.size == 0L ? -1 : token.size, hash: token.Sha1);
|
|||
|
|
if (checker.Check(token.LocalPath) is null)
|
|||
|
|
continue;
|
|||
|
|
if (token.IsLocal)
|
|||
|
|
{
|
|||
|
|
ModBase.Log("[Download] 已跳过被标记为本地文件的支持库: " + token.OriginalName);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// URL
|
|||
|
|
var urls = new List<string>();
|
|||
|
|
if (token.Url is null && token.Name == "net.minecraftforge:forge:universal")
|
|||
|
|
// 特判修复 Forge 部分 universal 文件缺失 URL(#5455)
|
|||
|
|
token.Url = "https://maven.minecraftforge.net" +
|
|||
|
|
token.LocalPath.Replace(customMcFolder + "libraries", "").Replace(@"\", "/");
|
|||
|
|
if (token.Url is not null)
|
|||
|
|
{
|
|||
|
|
// 获取 URL 的真实地址
|
|||
|
|
urls.Add(token.Url);
|
|||
|
|
if (token.Url.Contains("launcher.mojang.com/v1/objects") || token.Url.Contains("client.txt") ||
|
|||
|
|
token.Url.Contains(".tsrg"))
|
|||
|
|
urls.AddRange(ModDownload.DlSourceLauncherOrMetaGet(token.Url)); // Mappings(#4425)
|
|||
|
|
if (token.Url.Contains("maven"))
|
|||
|
|
{
|
|||
|
|
var bmclapiUrl = token.Url
|
|||
|
|
.Replace(token.Url.Substring(0, token.Url.IndexOfF("maven")),
|
|||
|
|
"https://bmclapi2.bangbang93.com/").Replace("maven.fabricmc.net", "maven")
|
|||
|
|
.Replace("maven.minecraftforge.net", "maven").Replace("maven.neoforged.net/releases", "maven");
|
|||
|
|
if (ModDownload.DlSourcePreferMojang)
|
|||
|
|
urls.Add(bmclapiUrl); // 官方源优先
|
|||
|
|
else
|
|||
|
|
urls.Insert(0, bmclapiUrl); // 镜像源优先
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (token.LocalPath.Contains("transformer-discovery-service"))
|
|||
|
|
{
|
|||
|
|
// Transformer 文件释放
|
|||
|
|
if (!File.Exists(token.LocalPath))
|
|||
|
|
ModBase.WriteFile(token.LocalPath, ModBase.GetResourceStream("Resources/transformer.jar"));
|
|||
|
|
ModBase.Log("[Download] 已自动释放 Transformer Discovery Service", ModBase.LogLevel.Developer);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (token.LocalPath.Contains(@"optifine\OptiFine"))
|
|||
|
|
{
|
|||
|
|
// OptiFine 主 Jar
|
|||
|
|
var optiFineBase =
|
|||
|
|
token.LocalPath.Replace(Path.Combine(customMcFolder, "libraries", "optifine", "OptiFine") + @"\", "").Split("_")[0] + "/" +
|
|||
|
|
ModBase.GetFileNameFromPath(token.LocalPath).Replace("-", "_");
|
|||
|
|
optiFineBase = "/maven/com/optifine/" + optiFineBase;
|
|||
|
|
if (optiFineBase.Contains("_pre"))
|
|||
|
|
optiFineBase = optiFineBase.Replace("com/optifine/", "com/optifine/preview_");
|
|||
|
|
urls.Add("https://bmclapi2.bangbang93.com" + optiFineBase);
|
|||
|
|
}
|
|||
|
|
else if (token.Name.Contains("LabyMod"))
|
|||
|
|
{
|
|||
|
|
// LabyMod 只有一个下载源
|
|||
|
|
urls.Add(token.Url);
|
|||
|
|
ModBase.Log(
|
|||
|
|
$"[Download] 获取到 LabyMod 主要库文件的 Size = {token.size},SHA1 = {token.Sha1},由于 LabyMod 乱写 Size,已忽略 Size");
|
|||
|
|
checker = new ModBase.FileChecker(hash: token.Sha1); // 只校验 SHA1
|
|||
|
|
}
|
|||
|
|
else if (urls.Count <= 2)
|
|||
|
|
{
|
|||
|
|
// 普通文件
|
|||
|
|
urls.AddRange(ModDownload.DlSourceLibraryGet("https://libraries.minecraft.net" +
|
|||
|
|
token.LocalPath.Replace(customMcFolder + "libraries", "")
|
|||
|
|
.Replace(@"\", "/")));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result.Add(new DownloadFile(urls.Distinct(), token.LocalPath, checker));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 去重并返回
|
|||
|
|
return result.Distinct((a, b) => (a.LocalPath ?? "") == (b.LocalPath ?? ""));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取对应的支持库文件地址。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="original">原始地址,如 com.mumfrey:liteloader:1.12.2-SNAPSHOT。</param>
|
|||
|
|
/// <param name="withHead">是否包含 Lib 文件夹头部,若不包含,则会类似以 com\xxx\ 开头。</param>
|
|||
|
|
public static string McLibGet(string original, bool withHead = true, bool ignoreLiteLoader = false,
|
|||
|
|
string customMcFolder = null)
|
|||
|
|
{
|
|||
|
|
string mcLibGetRet = default;
|
|||
|
|
customMcFolder = customMcFolder ?? ModFolder.mcFolderSelected;
|
|||
|
|
var splited = original.Split(":");
|
|||
|
|
mcLibGetRet = withHead
|
|||
|
|
? Path.Combine(customMcFolder, "libraries", splited[0].Replace(".", @"\"), splited[1], splited[2], splited[1] + "-" + splited[2] + ".jar")
|
|||
|
|
: Path.Combine(splited[0].Replace(".", @"\"), splited[1], splited[2], splited[1] + "-" + splited[2] + ".jar");
|
|||
|
|
// 判断 OptiFine 是否应该使用 installer
|
|||
|
|
if (mcLibGetRet.Contains(@"optifine\OptiFine\1.") && splited[2].Split(".").Count() > 1)
|
|||
|
|
{
|
|||
|
|
var majorVersion = (int)Math.Round(ModBase.Val(splited[2].Split(".")[1].BeforeFirst("_")));
|
|||
|
|
var minorVersion = (int)Math.Round(splited[2].Split(".").Count() > 2
|
|||
|
|
? ModBase.Val(splited[2].Split(".")[2].BeforeFirst("_"))
|
|||
|
|
: 0d);
|
|||
|
|
if ((majorVersion == 12 || (majorVersion == 20 && minorVersion >= 4) || majorVersion >= 21) && File.Exists(
|
|||
|
|
$@"{customMcFolder}libraries\{splited[0].Replace(".", @"\")}\{splited[1]}\{splited[2]}\{splited[1]}-{splited[2]}-installer.jar")) // 仅在 1.12 (无法追溯) 和 1.20.4+ (#5376) 遇到此问题
|
|||
|
|
{
|
|||
|
|
ModLaunch.McLaunchLog("已将 " + original + " 替换为对应的 Installer 文件");
|
|||
|
|
mcLibGetRet = mcLibGetRet.Replace(".jar", "-installer.jar");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return mcLibGetRet;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 根据 JSON 中的 downloads.artifact.path 获取支持库文件地址。
|
|||
|
|
/// </summary>
|
|||
|
|
private static string McLibGetByArtifactPath(string artifactPath, string customMcFolder = null)
|
|||
|
|
{
|
|||
|
|
customMcFolder = customMcFolder ?? ModFolder.mcFolderSelected;
|
|||
|
|
if (string.IsNullOrWhiteSpace(artifactPath))
|
|||
|
|
throw new ArgumentException("支持库下载路径无效", nameof(artifactPath));
|
|||
|
|
|
|||
|
|
var librariesRoot = Path.GetFullPath(Path.Combine(customMcFolder, "libraries"));
|
|||
|
|
var normalizedArtifactPath = artifactPath.Replace('/', Path.DirectorySeparatorChar);
|
|||
|
|
if (Path.IsPathRooted(normalizedArtifactPath))
|
|||
|
|
throw new IOException("支持库下载路径不能为绝对路径: " + artifactPath);
|
|||
|
|
|
|||
|
|
var localPath = Path.GetFullPath(Path.Combine(librariesRoot, normalizedArtifactPath));
|
|||
|
|
var librariesRootWithSeparator = librariesRoot.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) +
|
|||
|
|
Path.DirectorySeparatorChar;
|
|||
|
|
if (!localPath.StartsWith(librariesRootWithSeparator, StringComparison.OrdinalIgnoreCase))
|
|||
|
|
throw new IOException("支持库下载路径不能位于 libraries 文件夹外: " + artifactPath);
|
|||
|
|
|
|||
|
|
return localPath;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 检查设置,是否应当忽略文件检查?
|
|||
|
|
/// </summary>
|
|||
|
|
public static bool ShouldIgnoreFileCheck(McInstance version)
|
|||
|
|
{
|
|||
|
|
return Config.Instance.DisableAssetVerifyV2[version.PathInstance] ||
|
|||
|
|
Config.Instance.AssetVerifySolutionV1[version.PathInstance] == 2;
|
|||
|
|
}
|
|||
|
|
}
|