CI / Go Backend (push) Canceled after 0s
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
1063 lines
46 KiB
C#
1063 lines
46 KiB
C#
using System.Globalization;
|
||
using System.IO;
|
||
using System.IO.Compression;
|
||
using System.Text.RegularExpressions;
|
||
using PCL.Core.App;
|
||
using PCL.Core.App.Localization;
|
||
using PCL.Core.Utils;
|
||
|
||
namespace PCL;
|
||
|
||
public class McInstance
|
||
{
|
||
/// <summary>
|
||
/// 显示的描述文本。
|
||
/// </summary>
|
||
public string Desc = Lang.Text("Select.Instance.Description.NotLoaded");
|
||
|
||
/// <summary>
|
||
/// 强制实例分类,0 为未启用,1 为隐藏,2 及以上为其他普通分类。
|
||
/// </summary>
|
||
public McInstanceCardType displayType = McInstanceCardType.Auto;
|
||
|
||
public bool IsLoaded;
|
||
|
||
/// <summary>
|
||
/// 是否已初始化从 JAR 中读取 version.json。
|
||
/// </summary>
|
||
private bool _jsonVersionInited;
|
||
|
||
/// <summary>
|
||
/// 是否为收藏的实例。
|
||
/// </summary>
|
||
public bool IsStar;
|
||
|
||
/// <summary>
|
||
/// 显示的实例图标。
|
||
/// </summary>
|
||
public string Logo;
|
||
|
||
/// <summary>
|
||
/// 实例的发布时间。
|
||
/// </summary>
|
||
public DateTime releaseTime = new(1970, 1, 1, 15, 0, 0);
|
||
|
||
/// <summary>
|
||
/// 该实例的列表检查原始结果,不受自定义影响。
|
||
/// </summary>
|
||
public McInstanceState state = McInstanceState.Error;
|
||
|
||
/// <summary></summary>
|
||
/// <param name="name">实例名,或实例文件夹的完整路径(不规定是否以 \ 结尾)。</param>
|
||
public McInstance(string name)
|
||
{
|
||
PathInstance = (name.Contains(":") ? name : Path.Combine(ModFolder.mcFolderSelected, "versions", name)) + (name.EndsWithF(@"\") ? "" : @"\");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 该实例的实例文件夹,以“\”结尾。
|
||
/// </summary>
|
||
public string PathInstance { get; }
|
||
|
||
/// <summary>
|
||
/// 应用版本隔离后,该实例所对应的 Minecraft 根文件夹,以“\”结尾。
|
||
/// </summary>
|
||
public string PathIndie
|
||
{
|
||
get
|
||
{
|
||
if (Config.Instance.IndieV2Config.IsDefault(PathInstance))
|
||
{
|
||
if (!IsLoaded)
|
||
Load();
|
||
|
||
// 决定该实例是否应该被隔离
|
||
bool ShouldBeIndie()
|
||
{
|
||
// 从老的实例独立设置中迁移:-1 未决定,0 使用全局设置,1 手动开启,2 手动关闭
|
||
if (!Config.Instance.IndieV1Config.IsDefault(PathInstance) && Config.Instance.IndieV1[PathInstance] > 0)
|
||
{
|
||
ModBase.Log($"[Minecraft] 版本隔离初始化({Name}):从老的实例独立设置中迁移");
|
||
return Config.Instance.IndieV1[PathInstance] == 1;
|
||
}
|
||
|
||
// 若实例文件夹下包含 mods 或 saves 文件夹,则自动开启版本隔离
|
||
var modFolder = new DirectoryInfo(PathInstance + @"mods\");
|
||
var saveFolder = new DirectoryInfo(PathInstance + @"saves\");
|
||
if ((modFolder.Exists && modFolder.EnumerateFiles().Any()) ||
|
||
(saveFolder.Exists && saveFolder.EnumerateDirectories().Any()))
|
||
{
|
||
ModBase.Log($"[Minecraft] 版本隔离初始化({Name}):实例文件夹下存在 mods 或 saves 文件夹,自动开启");
|
||
return true;
|
||
}
|
||
|
||
// 根据全局的默认设置决定是否隔离
|
||
var isRelease = state != McInstanceState.Fool && state != McInstanceState.Old &&
|
||
state != McInstanceState.Snapshot;
|
||
ModBase.Log(
|
||
$"[Minecraft] 版本隔离初始化({Name}):从全局默认设置中({Config.Launch.IndieSolutionV2})判断,State {ModBase.GetStringFromEnum(state)},IsRelease {isRelease},Modable {Modable}");
|
||
|
||
return Config.Launch.IndieSolutionV2 switch
|
||
{
|
||
0 => false, // 关闭
|
||
1 => Info.HasLabyMod || Modable, // 仅隔离可安装 Mod 的实例
|
||
2 => !isRelease, // 仅隔离非正式版
|
||
3 => Info.HasLabyMod || Modable || !isRelease, // 隔离非正式版与可安装 Mod 的实例
|
||
_ => true // 隔离所有实例
|
||
};
|
||
}
|
||
|
||
Config.Instance.IndieV2[PathInstance] = ShouldBeIndie();
|
||
}
|
||
|
||
return Config.Instance.IndieV2[PathInstance] ? PathInstance : ModFolder.mcFolderSelected;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 该实例的实例文件夹名称。
|
||
/// </summary>
|
||
public string Name
|
||
{
|
||
get
|
||
{
|
||
if (field is null && !string.IsNullOrEmpty(PathInstance))
|
||
field = ModBase.GetFolderNameFromPath(PathInstance);
|
||
return field;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 该实例是否可以安装 Mod。
|
||
/// </summary>
|
||
public bool Modable
|
||
{
|
||
get
|
||
{
|
||
if (!IsLoaded)
|
||
Load();
|
||
return Info.HasFabric || Info.HasLegacyFabric || Info.HasQuilt || Info.HasForge || Info.HasLiteLoader ||
|
||
Info.HasNeoForge || Info.HasCleanroom || displayType == McInstanceCardType.API; // #223
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 实例信息。
|
||
/// </summary>
|
||
public McInstanceInfo Info
|
||
{
|
||
get
|
||
{
|
||
if (field is not null)
|
||
return field;
|
||
field = new McInstanceInfo();
|
||
|
||
#region 获取游戏版本
|
||
|
||
try
|
||
{
|
||
// 获取发布时间并判断是否为老版本
|
||
try
|
||
{
|
||
if (JsonObject["releaseTime"] is null)
|
||
releaseTime = new DateTime(1970, 1, 1, 15, 0, 0); // 未知版本也可能显示为 1970 年
|
||
else
|
||
releaseTime = JsonObject["releaseTime"].ToObject<DateTime>();
|
||
if (releaseTime.Year > 2000 && releaseTime.Year < 2013)
|
||
{
|
||
field.VanillaName = "Old";
|
||
goto VersionSearchFinish;
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
releaseTime = new DateTime(1970, 1, 1, 15, 0, 0);
|
||
}
|
||
|
||
// 实验性快照
|
||
if ((string)(JsonObject["type"] ?? "") == "pending")
|
||
{
|
||
field.VanillaName = "pending";
|
||
goto VersionSearchFinish;
|
||
}
|
||
|
||
// 从 PCL 下载的版本信息中获取版本号
|
||
if (JsonObject["clientVersion"] is not null)
|
||
{
|
||
field.VanillaName = (string)JsonObject["clientVersion"];
|
||
goto VersionSearchFinish;
|
||
}
|
||
|
||
// 从 HMCL 下载的版本信息中获取版本号
|
||
if (JsonObject["patches"] is not null)
|
||
foreach (var patchNode in JsonObject["patches"].AsArray()) { var patch = patchNode.AsObject();
|
||
if ((patch["id"] ?? "").ToString() == "game" && patch["version"] is not null)
|
||
{
|
||
field.VanillaName = patch["version"].ToString();
|
||
goto VersionSearchFinish;
|
||
} }
|
||
|
||
// 从 Forge / NeoForge / LabyMod Arguments 中获取版本号
|
||
if (JsonObject["arguments"] is not null)
|
||
{
|
||
if (JsonObject["arguments"]["game"] is not null)
|
||
{
|
||
var mark = false;
|
||
foreach (var Argument in JsonObject["arguments"]["game"].AsArray())
|
||
{
|
||
if (mark)
|
||
{
|
||
field.VanillaName = Argument.ToString();
|
||
goto VersionSearchFinish;
|
||
}
|
||
|
||
if (Argument.ToString() == "--fml.mcVersion")
|
||
mark = true;
|
||
}
|
||
}
|
||
|
||
if (JsonObject["arguments"]["jvm"] is not null)
|
||
foreach (var Argument in JsonObject["arguments"]["jvm"].AsArray())
|
||
{
|
||
var regexArgument = Argument.ToString().RegexSeek(RegexPatterns.LabyModVersion);
|
||
if (regexArgument is not null)
|
||
{
|
||
field.VanillaName = regexArgument;
|
||
goto VersionSearchFinish;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 从继承实例中获取版本号
|
||
if (!string.IsNullOrEmpty(InheritInstanceName))
|
||
{
|
||
field.VanillaName = (JsonObject["jar"] ?? "").ToString(); // LiteLoader 优先使用 Jar
|
||
if (string.IsNullOrEmpty(field.VanillaName))
|
||
field.VanillaName = InheritInstanceName;
|
||
goto VersionSearchFinish;
|
||
}
|
||
|
||
// 从下载地址中获取版本号
|
||
var regex = (JsonObject["downloads"] ?? "").ToString()
|
||
.RegexSeek(RegexPatterns.MinecraftDownloadUrlVersion);
|
||
if (regex is not null)
|
||
{
|
||
field.VanillaName = regex;
|
||
goto VersionSearchFinish;
|
||
}
|
||
|
||
// 从 Forge 版本中获取版本号
|
||
var librariesString = JsonObject["libraries"].ToString();
|
||
regex = librariesString.RegexSeek(RegexPatterns.ForgeLibVersion);
|
||
if (regex is not null)
|
||
{
|
||
field.VanillaName = regex;
|
||
goto VersionSearchFinish;
|
||
}
|
||
|
||
// 从 OptiFine 版本中获取版本号
|
||
regex = librariesString.RegexSeek(RegexPatterns.OptiFineLibVersion);
|
||
if (regex is not null)
|
||
{
|
||
field.VanillaName = regex;
|
||
goto VersionSearchFinish;
|
||
}
|
||
|
||
// 从 Fabric / Quilt / Legacy Fabric 版本中获取版本号
|
||
regex = librariesString.RegexSeek(RegexPatterns.FabricLikeLibVersion);
|
||
if (regex is not null)
|
||
{
|
||
field.VanillaName = regex;
|
||
goto VersionSearchFinish;
|
||
}
|
||
|
||
// 从 jar 项中获取版本号
|
||
if (JsonObject["jar"] is not null)
|
||
{
|
||
field.VanillaName = JsonObject["jar"].ToString();
|
||
goto VersionSearchFinish;
|
||
}
|
||
|
||
// 从 jar 文件的 version.json 中获取版本号
|
||
if (JsonVersion?["name"] is not null)
|
||
{
|
||
var jsonVerName = JsonVersion["name"].ToString();
|
||
if (jsonVerName.Length < 32) // 因为 wiki 说这玩意儿可能是个 hash,虽然我没发现
|
||
{
|
||
field.VanillaName = jsonVerName;
|
||
ModBase.Log("[Minecraft] 从版本 jar 中的 version.json 获取到版本号:" + jsonVerName);
|
||
goto VersionSearchFinish;
|
||
}
|
||
}
|
||
|
||
// 从 JSON 的 ID 中获取
|
||
regex = ((string)JsonObject["id"]).RegexSeek(RegexPatterns.MinecraftJsonVersion,
|
||
RegexOptions.IgnoreCase);
|
||
if (regex is not null)
|
||
{
|
||
field.VanillaName = regex;
|
||
goto VersionSearchFinish;
|
||
}
|
||
|
||
// 非准确的版本判断警告
|
||
ModBase.Log("[Minecraft] 无法完全确认 MC 版本号的版本:" + Name);
|
||
field.Reliable = false;
|
||
// 从文件夹名中获取
|
||
regex = Name.RegexSeek(RegexPatterns.MinecraftJsonVersion, RegexOptions.IgnoreCase);
|
||
if (regex is not null)
|
||
{
|
||
field.VanillaName = regex;
|
||
goto VersionSearchFinish;
|
||
}
|
||
|
||
// 从 JSON 出现的版本号中获取
|
||
var jsonRaw = (JsonObject)JsonObject.DeepClone();
|
||
jsonRaw.Remove("libraries");
|
||
var jsonRawText = jsonRaw.ToString();
|
||
regex = jsonRawText.RegexSeek(RegexPatterns.MinecraftJsonVersion, RegexOptions.IgnoreCase);
|
||
if (regex is not null)
|
||
{
|
||
field.VanillaName = regex;
|
||
goto VersionSearchFinish;
|
||
}
|
||
|
||
// 无法获取
|
||
field.VanillaName = "Unknown";
|
||
Desc = Lang.Text("Select.Instance.Description.UnknownMcVersion");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(ex, "识别 Minecraft 版本时出错");
|
||
field.VanillaName = "Unknown";
|
||
Desc = Lang.Text("Minecraft.Error.Unrecognizable", ex.Message);
|
||
}
|
||
|
||
#endregion
|
||
|
||
VersionSearchFinish: ;
|
||
|
||
if (field.VanillaName.StartsWithF("20.") || field.VanillaName.StartsWithF("21."))
|
||
{
|
||
field.VanillaName = "1." + field.VanillaName;
|
||
}
|
||
|
||
field.VanillaName = field.VanillaName.Replace("_unobfuscated", "").Replace(" Unobfuscated", "");
|
||
// 获取版本号
|
||
if (field.VanillaName.StartsWithF("1."))
|
||
{
|
||
var segments = field.VanillaName.Split(" _-.".ToCharArray());
|
||
field.vanilla = new Version((int)Math.Round(ModBase.Val(segments.Count() >= 2 ? segments[1] : "0")),
|
||
0, (int)Math.Round(ModBase.Val(segments.Count() >= 3 ? segments[2] : "0")));
|
||
}
|
||
else if (field.VanillaName.RegexCheck(@"^[2-9][0-9]\."))
|
||
{
|
||
var segments = field.VanillaName.Split(" _-.".ToCharArray());
|
||
field.vanilla = new Version((int)Math.Round(ModBase.Val(segments[0])),
|
||
(int)Math.Round(ModBase.Val(segments.Count() >= 2 ? segments[1] : "0")),
|
||
(int)Math.Round(ModBase.Val(segments.Count() >= 3 ? segments[2] : "0")));
|
||
}
|
||
else
|
||
{
|
||
field.vanilla = new Version(9999, 0, 0);
|
||
}
|
||
|
||
return field;
|
||
}
|
||
set { field = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 该实例的 JSON 文本。
|
||
/// </summary>
|
||
public string JsonText
|
||
{
|
||
get
|
||
{
|
||
// 快速检查 JSON 是否以 { 开头、} 结尾;忽略空白字符
|
||
bool FastJsonCheck(string json)
|
||
{
|
||
var trimedJson = json.Trim();
|
||
return trimedJson.StartsWithF("{") && trimedJson.EndsWithF("}");
|
||
}
|
||
|
||
;
|
||
if (field is null)
|
||
{
|
||
var jsonPath = PathInstance + Name + ".json";
|
||
if (!File.Exists(jsonPath))
|
||
{
|
||
// 如果文件夹下只有一个 JSON 文件,则将其作为实例 JSON
|
||
var jsonFiles = Directory.GetFiles(PathInstance, "*.json");
|
||
if (jsonFiles.Count() == 1)
|
||
{
|
||
jsonPath = jsonFiles[0];
|
||
ModBase.Log("[Minecraft] 未找到同名实例 JSON,自动换用 " + jsonPath, ModBase.LogLevel.Debug);
|
||
}
|
||
else
|
||
{
|
||
throw new Exception(Lang.Text("Minecraft.Error.InstanceJsonNotFound",
|
||
$"{PathInstance}{Name}.json"));
|
||
}
|
||
}
|
||
|
||
field = ModBase.ReadFile(jsonPath);
|
||
// 如果 ReadFile 失败会返回空字符串;这可能是由于文件被临时占用,故延时后重试
|
||
if (!FastJsonCheck(field))
|
||
{
|
||
if (ModBase.RunInUi())
|
||
{
|
||
ModBase.Log($"[Minecraft] 实例 JSON 文件为空或有误,将进行短暂重试({jsonPath})", ModBase.LogLevel.Debug);
|
||
Thread.Sleep(200);
|
||
field = ModBase.ReadFile(jsonPath);
|
||
}
|
||
else
|
||
{
|
||
ModBase.Log($"[Minecraft] 实例 JSON 文件为空或有误,将在 2s 后重试读取({jsonPath})", ModBase.LogLevel.Debug);
|
||
Thread.Sleep(2000);
|
||
field = ModBase.ReadFile(jsonPath);
|
||
}
|
||
if (!FastJsonCheck(field))
|
||
ModBase.GetJson(field);
|
||
}
|
||
}
|
||
|
||
return field;
|
||
}
|
||
set => field = value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 该实例的 JSON 对象。
|
||
/// 若 JSON 存在问题,在获取该属性时即会抛出异常。
|
||
/// </summary>
|
||
public JsonObject JsonObject
|
||
{
|
||
get
|
||
{
|
||
if (field is null)
|
||
{
|
||
var text = JsonText; // 触发 JsonText 的 Get 事件
|
||
try
|
||
{
|
||
field = (JsonObject)ModBase.GetJson(text);
|
||
// 转换 HMCL 关键项
|
||
if (field.ContainsKey("patches") && !field.ContainsKey("time"))
|
||
{
|
||
IsHmclFormatJson = true;
|
||
// 合并 JSON
|
||
// Dim HasOptiFine As Boolean = False, HasForge As Boolean = False
|
||
JsonObject currentObject = null;
|
||
var subjsonList = new List<JsonObject>();
|
||
foreach (var SubjsonNode in field["patches"].AsArray()) { var subjson = SubjsonNode.AsObject();
|
||
subjsonList.Add(subjson); }
|
||
subjsonList.Sort((left, right) =>
|
||
{
|
||
var leftVal = ModBase.Val((left["priority"] ?? "0").ToString());
|
||
var rightVal = ModBase.Val((right["priority"] ?? "0").ToString());
|
||
return leftVal.CompareTo(rightVal);
|
||
});
|
||
foreach (var Subjson in subjsonList)
|
||
{
|
||
var id = (string)Subjson["id"];
|
||
if (id is not null)
|
||
{
|
||
// 合并 JSON
|
||
ModBase.Log("[Minecraft] 合并 HMCL 分支项:" + id);
|
||
if (currentObject is not null)
|
||
currentObject.Merge(Subjson);
|
||
else
|
||
currentObject = Subjson;
|
||
}
|
||
else
|
||
{
|
||
ModBase.Log("[Minecraft] 存在为空的 HMCL 分支项");
|
||
}
|
||
}
|
||
|
||
field = currentObject;
|
||
// 修改附加项
|
||
field["id"] = Name;
|
||
if (field.ContainsKey("inheritsFrom"))
|
||
field.Remove("inheritsFrom");
|
||
}
|
||
|
||
// 与继承实例合并
|
||
object inheritInstanceName = null;
|
||
do
|
||
{
|
||
try
|
||
{
|
||
inheritInstanceName = field["inheritsFrom"] is null
|
||
? ""
|
||
: field["inheritsFrom"].ToString();
|
||
if (Equals(inheritInstanceName, Name))
|
||
{
|
||
ModBase.Log("[Minecraft] 自引用的继承实例:" + Name, ModBase.LogLevel.Debug);
|
||
inheritInstanceName = "";
|
||
break;
|
||
}
|
||
|
||
Recheck: ;
|
||
|
||
if (!Equals(inheritInstanceName, ""))
|
||
{
|
||
var inheritInstance = new McInstance(inheritInstanceName?.ToString() ?? "");
|
||
// 继续循环
|
||
if (Equals(inheritInstance.InheritInstanceName,
|
||
inheritInstanceName))
|
||
throw new Exception(Lang.Text("Minecraft.Error.DependencyRecursion",
|
||
inheritInstanceName));
|
||
inheritInstanceName = inheritInstance.InheritInstanceName;
|
||
// 合并
|
||
inheritInstance.JsonObject.Merge(field);
|
||
field = inheritInstance.JsonObject;
|
||
goto Recheck;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(ex, "合并实例依赖项 JSON 失败(" + (inheritInstanceName ?? "null") + ")");
|
||
}
|
||
} while (false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception(Lang.Text("Minecraft.Error.InitInstanceJsonFailed", Name ?? "null"), ex);
|
||
}
|
||
}
|
||
|
||
return field;
|
||
}
|
||
set => field = value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否为旧版 JSON 格式。
|
||
/// </summary>
|
||
public bool IsOldJson => JsonObject["minecraftArguments"] is not null &&
|
||
(string)JsonObject["minecraftArguments"] != "";
|
||
|
||
/// <summary>
|
||
/// JSON 是否为 HMCL 格式。
|
||
/// </summary>
|
||
public bool IsHmclFormatJson { get; set; }
|
||
|
||
/// <summary>
|
||
/// 实例 JAR 中的 version.json 文件对象。
|
||
/// 若没有则返回 Nothing。
|
||
/// </summary>
|
||
public JsonObject JsonVersion
|
||
{
|
||
get
|
||
{
|
||
if (!_jsonVersionInited)
|
||
{
|
||
_jsonVersionInited = true;
|
||
do
|
||
{
|
||
try
|
||
{
|
||
if (!File.Exists(PathInstance + Name + ".jar"))
|
||
break;
|
||
using (var jarArchive = new ZipArchive(new FileStream(PathInstance + Name + ".jar",
|
||
FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
|
||
{
|
||
var versionJson = jarArchive.GetEntry("version.json");
|
||
if (versionJson is not null)
|
||
using (var versionJsonStream = new StreamReader(versionJson.Open()))
|
||
{
|
||
field = (JsonObject)ModBase.GetJson(versionJsonStream.ReadToEnd());
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(ex, $"从实例 JAR 中读取 version.json 失败 ({PathInstance}{Name}.jar)");
|
||
}
|
||
} while (false);
|
||
}
|
||
|
||
return field;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 该实例的依赖实例。若无依赖实例则为空字符串。
|
||
/// </summary>
|
||
public string InheritInstanceName
|
||
{
|
||
get
|
||
{
|
||
if (field is null)
|
||
{
|
||
field = (JsonObject["inheritsFrom"] ?? "").ToString();
|
||
// 由于过老的 LiteLoader 中没有 Inherits(例如 1.5.2),需要手动判断以获取真实继承实例
|
||
// 此外,由于这里的加载早于实例种类判断,所以需要手动判断是否为 LiteLoader
|
||
// 如果实例提供了不同的 JAR,代表所需的 JAR 可能已被更改,则跳过 Inherit 替换
|
||
if (JsonText.Contains("liteloader") && (Info.VanillaName ?? "") != (Name ?? "") &&
|
||
!JsonText.Contains("logging"))
|
||
if (((JsonObject["jar"] ?? Info.VanillaName).ToString() ?? "") == (Info.VanillaName ?? ""))
|
||
field = Info.VanillaName;
|
||
// HMCL 实例无 JSON
|
||
if (IsHmclFormatJson)
|
||
field = "";
|
||
}
|
||
|
||
return field;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查 Minecraft 版本,若检查通过 State 则为 Original 且返回 True。
|
||
/// </summary>
|
||
public bool Check()
|
||
{
|
||
// 检查文件夹
|
||
if (!Directory.Exists(PathInstance))
|
||
{
|
||
state = McInstanceState.Error;
|
||
Desc = Lang.Text("Select.Instance.Description.NotFound", Name);
|
||
return false;
|
||
}
|
||
|
||
// 检查权限
|
||
try
|
||
{
|
||
Directory.CreateDirectory(PathInstance + @"PCL\");
|
||
ModBase.CheckPermissionWithException(PathInstance + @"PCL\");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
state = McInstanceState.Error;
|
||
Desc = Lang.Text("Select.Instance.Description.NoPermission");
|
||
ModBase.Log(ex, "没有访问实例文件夹的权限");
|
||
return false;
|
||
}
|
||
|
||
// 确认 JSON 可用性
|
||
try
|
||
{
|
||
var jsonObjCheck = JsonObject;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(ex, "实例 JSON 可用性检查失败(" + PathInstance + ")");
|
||
JsonText = "";
|
||
JsonObject = null;
|
||
Desc = ex.Message;
|
||
state = McInstanceState.Error;
|
||
return false;
|
||
}
|
||
|
||
// 检查版本号获取
|
||
try
|
||
{
|
||
if (string.IsNullOrEmpty(Info.VanillaName))
|
||
throw new Exception(Lang.Text("Minecraft.Error.VersionNumberEmpty"));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(ex, "版本号获取失败(" + Name + ")");
|
||
state = McInstanceState.Error;
|
||
Desc = Lang.Text("Minecraft.Error.VersionNumberFetchFailed", ex);
|
||
return false;
|
||
}
|
||
|
||
// 检查依赖实例
|
||
try
|
||
{
|
||
if (!string.IsNullOrEmpty(InheritInstanceName))
|
||
if (!File.Exists(Path.Combine(ModBase.GetPathFromFullPath(PathInstance), InheritInstanceName, InheritInstanceName + ".json")))
|
||
{
|
||
state = McInstanceState.Error;
|
||
Desc = Lang.Text("Select.Instance.Description.NeedInherit", InheritInstanceName);
|
||
return false;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(ex, "依赖实例检查出错(" + Name + ")");
|
||
state = McInstanceState.Error;
|
||
Desc = Lang.Text("Select.Instance.Description.UnknownError") + ": " + ex;
|
||
return false;
|
||
}
|
||
|
||
state = McInstanceState.Original;
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载 Minecraft 实例的详细信息。不使用其缓存,且会更新缓存。
|
||
/// </summary>
|
||
public McInstance Load()
|
||
{
|
||
try
|
||
{
|
||
// 检查实例,若出错则跳过数据确定阶段
|
||
if (!Check())
|
||
goto ExitDataLoad;
|
||
|
||
#region 确定实例分类
|
||
|
||
switch (Info.VanillaName ?? "") // 在获取 Version.Original 对象时会完成它的加载
|
||
{
|
||
case "Unknown":
|
||
{
|
||
state = McInstanceState.Error;
|
||
break;
|
||
}
|
||
case "Old":
|
||
{
|
||
state = McInstanceState.Old; // 根据 API 进行筛选
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
var realJson = JsonObject is not null ? JsonObject.ToString() : JsonText;
|
||
// 愚人节与快照版本
|
||
if ((JsonObject["type"] ?? "").ToString() == "fool" ||
|
||
!string.IsNullOrEmpty(McVersionClassifier.GetMcFoolName(Info.VanillaName)))
|
||
state = McInstanceState.Fool;
|
||
else if (IsSnapshot()) state = McInstanceState.Snapshot;
|
||
// OptiFine
|
||
if (realJson.Contains("optifine"))
|
||
{
|
||
state = McInstanceState.OptiFine;
|
||
Info.HasOptiFine = true;
|
||
Info.OptiFine = realJson.RegexSeek(RegexPatterns.OptiFineVersion) ??
|
||
Lang.Text("Minecraft.Version.Unknown");
|
||
}
|
||
|
||
// LiteLoader
|
||
if (realJson.Contains("liteloader"))
|
||
{
|
||
state = McInstanceState.LiteLoader;
|
||
Info.HasLiteLoader = true;
|
||
}
|
||
|
||
// Fabric、Forge、Quilt、LabyMod、Legacy Fabric
|
||
if (realJson.Contains("labymod_data"))
|
||
{
|
||
state = McInstanceState.LabyMod;
|
||
Info.HasLabyMod = true;
|
||
Info.LabyMod = (string)JsonObject["labymod_data"]["version"];
|
||
}
|
||
else if (realJson.Contains("net.legacyfabric:intermediary"))
|
||
{
|
||
state = McInstanceState.LegacyFabric;
|
||
Info.HasLegacyFabric = true;
|
||
Info.LegacyFabric =
|
||
(realJson.RegexSeek(RegexPatterns.LegacyFabricVersion) ??
|
||
Lang.Text("Minecraft.Version.Unknown"))
|
||
.Replace("+build", "");
|
||
}
|
||
else if (realJson.Contains("net.fabricmc:fabric-loader"))
|
||
{
|
||
state = McInstanceState.Fabric;
|
||
Info.HasFabric = true;
|
||
Info.Fabric =
|
||
(realJson.RegexSeek(RegexPatterns.FabricVersion) ??
|
||
Lang.Text("Minecraft.Version.Unknown")).Replace("+build", "");
|
||
}
|
||
else if (realJson.Contains("org.quiltmc:quilt-loader"))
|
||
{
|
||
state = McInstanceState.Quilt;
|
||
Info.HasQuilt = true;
|
||
Info.Quilt =
|
||
(realJson.RegexSeek(RegexPatterns.QuiltVersion) ??
|
||
Lang.Text("Minecraft.Version.Unknown")).Replace("+build", "");
|
||
}
|
||
else if (realJson.Contains("com.cleanroommc:cleanroom:"))
|
||
{
|
||
state = McInstanceState.Cleanroom;
|
||
Info.HasCleanroom = true;
|
||
Info.Cleanroom =
|
||
(realJson.RegexSeek(RegexPatterns.CleanroomVersion) ??
|
||
Lang.Text("Minecraft.Version.Unknown")).Replace("+build", "");
|
||
}
|
||
else if (realJson.Contains("minecraftforge") && !realJson.Contains("net.neoforge"))
|
||
{
|
||
state = McInstanceState.Forge;
|
||
Info.HasForge = true;
|
||
Info.Forge = realJson.RegexSeek(RegexPatterns.ForgeMainVersion) ??
|
||
realJson.RegexSeek(RegexPatterns.ForgeLibVersion) ??
|
||
Lang.Text("Minecraft.Version.Unknown");
|
||
}
|
||
else if (realJson.Contains("net.neoforge"))
|
||
{
|
||
// 1.20.1 JSON 范例:"--fml.forgeVersion", "47.1.99"
|
||
// 1.20.2+ JSON 范例:"--fml.neoForgeVersion", "20.6.119-beta"
|
||
state = McInstanceState.NeoForge;
|
||
Info.HasNeoForge = true;
|
||
Info.NeoForge = realJson.RegexSeek(RegexPatterns.NeoForgeVersion) ??
|
||
Lang.Text("Minecraft.Version.Unknown");
|
||
}
|
||
|
||
break;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
ExitDataLoad: ;
|
||
|
||
// 确定实例图标
|
||
Logo = States.Instance.LogoPath[PathInstance];
|
||
if (string.IsNullOrEmpty(Logo) || !States.Instance.IsLogoCustom[PathInstance])
|
||
switch (state)
|
||
{
|
||
case McInstanceState.Original:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/Grass.png";
|
||
break;
|
||
}
|
||
case McInstanceState.Snapshot:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/CommandBlock.png";
|
||
break;
|
||
}
|
||
case McInstanceState.Old:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/CobbleStone.png";
|
||
break;
|
||
}
|
||
case McInstanceState.Forge:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/Anvil.png";
|
||
break;
|
||
}
|
||
case McInstanceState.NeoForge:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/NeoForge.png";
|
||
break;
|
||
}
|
||
case McInstanceState.Cleanroom:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/Cleanroom.png";
|
||
break;
|
||
}
|
||
case McInstanceState.Fabric:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/Fabric.png";
|
||
break;
|
||
}
|
||
case McInstanceState.LegacyFabric:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/Fabric.png";
|
||
break;
|
||
}
|
||
case McInstanceState.Quilt:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/Quilt.png";
|
||
break;
|
||
}
|
||
case McInstanceState.OptiFine:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/GrassPath.png";
|
||
break;
|
||
}
|
||
case McInstanceState.LiteLoader:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/Egg.png";
|
||
break;
|
||
}
|
||
case McInstanceState.Fool:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/GoldBlock.png";
|
||
break;
|
||
}
|
||
case McInstanceState.LabyMod:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/LabyMod.png";
|
||
break;
|
||
}
|
||
|
||
default:
|
||
{
|
||
Logo = ModBase.pathImage + "Blocks/RedstoneBlock.png";
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 确定实例描述
|
||
if (state == McInstanceState.Error)
|
||
{
|
||
Desc = Desc;
|
||
}
|
||
else
|
||
{
|
||
Desc = States.Instance.CustomInfo[PathInstance];
|
||
if ((Desc ?? "") == (GetDefaultDescription() ?? ""))
|
||
Desc = "";
|
||
}
|
||
|
||
// 确定实例收藏状态
|
||
IsStar = States.Instance.Starred[PathInstance];
|
||
// 确定实例显示种类
|
||
displayType = (McInstanceCardType)States.Instance.CardType[PathInstance];
|
||
// 写入缓存
|
||
if (Directory.Exists(PathInstance))
|
||
{
|
||
States.Instance.State[PathInstance] = (int)state;
|
||
States.Instance.Info[PathInstance] = Desc;
|
||
States.Instance.LogoPath[PathInstance] = Logo;
|
||
}
|
||
|
||
if (state != McInstanceState.Error)
|
||
{
|
||
States.Instance.ReleaseTime[PathInstance] = releaseTime.ToString("yyyy'-'MM'-'dd HH':'mm", CultureInfo.InvariantCulture);
|
||
States.Instance.FabricVersion[PathInstance] = Info.Fabric;
|
||
States.Instance.LegacyFabricVersion[PathInstance] = Info.LegacyFabric;
|
||
States.Instance.QuiltVersion[PathInstance] = Info.Quilt;
|
||
States.Instance.LabyModVersion[PathInstance] = Info.LabyMod;
|
||
States.Instance.OptiFineVersion[PathInstance] = Info.OptiFine;
|
||
States.Instance.HasLiteLoader[PathInstance] = Info.HasLiteLoader;
|
||
States.Instance.ForgeVersion[PathInstance] = Info.Forge;
|
||
States.Instance.NeoForgeVersion[PathInstance] = Info.NeoForge;
|
||
States.Instance.CleanroomVersion[PathInstance] = Info.Cleanroom;
|
||
States.Instance.VanillaVersionName[PathInstance] = Info.VanillaName;
|
||
States.Instance.VanillaVersion[PathInstance] = Info.vanilla.ToString();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Desc = Lang.Text("Select.Instance.Description.UnknownError") + ": " + ex;
|
||
Logo = ModBase.pathImage + "Blocks/RedstoneBlock.png";
|
||
state = McInstanceState.Error;
|
||
ModBase.Log(
|
||
ex,
|
||
Lang.Text("Select.Instance.Error.Load", Name),
|
||
ModBase.LogLevel.Feedback,
|
||
userSummary: Lang.Text("Select.Instance.Error.Load", Name));
|
||
}
|
||
finally
|
||
{
|
||
IsLoaded = true;
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
private bool IsSnapshot()
|
||
{
|
||
return new[] { "w", "snapshot", "rc", "pre", "experimental", "-" }.Any(s =>
|
||
Info.VanillaName.ContainsF(s, true)) || Name.ContainsF("combat", true) ||
|
||
(JsonObject["type"] ?? "").ToString() == "snapshot" ||
|
||
(JsonObject["type"] ?? "").ToString() == "pending";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取实例的默认描述。
|
||
/// </summary>
|
||
public string GetDefaultDescription()
|
||
{
|
||
// Mod Loader 信息
|
||
var modLoaderInfo = "";
|
||
if (this.Info.HasForge)
|
||
modLoaderInfo += ", Forge" + (this.Info.Forge == Lang.Text("Minecraft.Version.Unknown")
|
||
? ""
|
||
: " " + this.Info.Forge);
|
||
if (this.Info.HasNeoForge)
|
||
modLoaderInfo += ", NeoForge" + (this.Info.NeoForge == Lang.Text("Minecraft.Version.Unknown")
|
||
? ""
|
||
: " " + this.Info.NeoForge);
|
||
if (this.Info.HasCleanroom)
|
||
modLoaderInfo += ", Cleanroom" + (this.Info.Cleanroom == Lang.Text("Minecraft.Version.Unknown")
|
||
? ""
|
||
: " " + this.Info.Cleanroom);
|
||
if (this.Info.HasLabyMod)
|
||
modLoaderInfo += ", LabyMod" + (this.Info.LabyMod == Lang.Text("Minecraft.Version.Unknown")
|
||
? ""
|
||
: " " + this.Info.LabyMod);
|
||
if (this.Info.HasFabric)
|
||
modLoaderInfo += ", Fabric" + (this.Info.Fabric == Lang.Text("Minecraft.Version.Unknown")
|
||
? ""
|
||
: " " + this.Info.Fabric);
|
||
if (this.Info.HasQuilt)
|
||
modLoaderInfo += ", Quilt" + (this.Info.Quilt == Lang.Text("Minecraft.Version.Unknown")
|
||
? ""
|
||
: " " + this.Info.Quilt);
|
||
if (this.Info.HasLegacyFabric)
|
||
modLoaderInfo += ", Legacy Fabric" +
|
||
(this.Info.LegacyFabric == Lang.Text("Minecraft.Version.Unknown")
|
||
? ""
|
||
: " " + this.Info.LegacyFabric);
|
||
if (this.Info.HasOptiFine)
|
||
modLoaderInfo += ", OptiFine" + (this.Info.OptiFine == Lang.Text("Minecraft.Version.Unknown")
|
||
? ""
|
||
: " " + this.Info.OptiFine.Replace("-", " ").Replace("_", " "));
|
||
if (this.Info.HasLiteLoader)
|
||
modLoaderInfo += ", LiteLoader";
|
||
// 基础信息
|
||
string info;
|
||
switch (state)
|
||
{
|
||
case McInstanceState.Snapshot:
|
||
case McInstanceState.Original:
|
||
case McInstanceState.Forge:
|
||
case McInstanceState.NeoForge:
|
||
case McInstanceState.Fabric:
|
||
case McInstanceState.OptiFine:
|
||
case McInstanceState.LiteLoader:
|
||
{
|
||
if (this.Info.VanillaName.ContainsF("pre", true))
|
||
info = Lang.Text("Select.Instance.Description.PreRelease", this.Info.VanillaName);
|
||
else if (this.Info.VanillaName.ContainsF("rc", true))
|
||
info = Lang.Text("Select.Instance.Description.ReleaseCandidate", this.Info.VanillaName);
|
||
else if (this.Info.VanillaName.Contains("experimental"))
|
||
info = Lang.Text("Select.Instance.Description.ExperimentalSnapshot", this.Info.VanillaName);
|
||
else if (this.Info.VanillaName == "pending")
|
||
info = Lang.Text("Select.Instance.Description.ExperimentalSnapshot.Pending");
|
||
else if (IsSnapshot())
|
||
info = this.Info.Reliable ? Lang.Text("Select.Instance.Description.Snapshot", this.Info.VanillaName.Replace("-snapshot", "")) : Lang.Text("Select.Instance.Description.Snapshot.Unknown");
|
||
else
|
||
info = this.Info.Reliable ? Lang.Text("Select.Instance.Description.Release", this.Info.VanillaName) : Lang.Text("Select.Instance.Description.Release.Unknown");
|
||
|
||
break;
|
||
}
|
||
case McInstanceState.Old:
|
||
{
|
||
info = Lang.Text("Select.Instance.Description.Old");
|
||
break;
|
||
}
|
||
case McInstanceState.Fool:
|
||
{
|
||
info = Lang.Text("Select.Instance.Description.AprilFools", this.Info.VanillaName);
|
||
break;
|
||
}
|
||
case McInstanceState.Error:
|
||
{
|
||
return Desc; // 已有错误信息
|
||
}
|
||
|
||
default:
|
||
{
|
||
return Lang.Text("Select.Instance.Description.ReportUnknownError");
|
||
}
|
||
}
|
||
|
||
return (info + modLoaderInfo).Replace("_", "-");
|
||
}
|
||
|
||
// 运算符支持
|
||
public override bool Equals(object obj)
|
||
{
|
||
var instance = obj as McInstance;
|
||
return instance is not null && (PathInstance ?? "") == (instance.PathInstance ?? "");
|
||
}
|
||
|
||
public static bool operator ==(McInstance? a, McInstance? b)
|
||
{
|
||
if (a is null && b is null)
|
||
return true;
|
||
if (a is null || b is null)
|
||
return false;
|
||
return (a.PathInstance ?? "") == (b.PathInstance ?? "");
|
||
}
|
||
|
||
public static bool operator !=(McInstance a, McInstance b)
|
||
{
|
||
return !(a == b);
|
||
}
|
||
} |