using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using PCL.Core.App;
using PCL.Core.App.Localization;
using PCL.Core.UI;
using PCL.Core.Utils;
using PCL.Core.Utils.Exts;
using PCL.Network;
namespace PCL;
public static class ModInstanceList
{
#region 实例处理
public const int mcInstanceCacheVersion = 30;
private static object _McInstanceSelected_mcInstanceSelectedLast = 0; // 为 0 以保证与 Nothing 不相同,使得 UI 显示可以正常初始化
///
/// 当前的 Minecraft 版本。
///
public static McInstance McMcInstanceSelected
{
get => field;
set
{
if (ReferenceEquals(_McInstanceSelected_mcInstanceSelectedLast, value))
return;
field = value; // 由于有可能是 Nothing,导致无法初始化,才得这样弄一圈
_McInstanceSelected_mcInstanceSelectedLast = value;
if (value is null)
return;
// 重置缓存的 Mod 文件夹
PageDownloadCompDetail.cachedFolder.Clear();
}
}
///
/// 当前按卡片分类的所有版本列表。
///
public static Dictionary> mcInstanceList = new();
#endregion
#region 实例列表加载
///
/// 是否要求本次加载强制刷新实例列表。
///
public static bool mcInstanceListForceRefresh;
///
/// 是否为本次打开 PCL 后第一次加载实例列表。
/// 这会清理所有 .pclignore 文件,而非跳过这些对应实例。
///
private static bool _isFirstMcInstanceListLoad = true;
///
/// 加载 Minecraft 文件夹的实例列表。
///
public static ModLoader.LoaderTask mcInstanceListLoader =
new("Minecraft Instance List", InitMcInstanceList) { reloadTimeout = 1 };
private static void InitMcInstanceList(ModLoader.LoaderTask loader)
{
var path = loader.input;
try
{
// 初始化
mcInstanceList = new Dictionary>();
var versionsPath = Path.Combine(path, "versions");
var folderList = new List();
// 读取版本文件夹
if (Directory.Exists(versionsPath))
try
{
foreach (var folder in new DirectoryInfo(versionsPath).GetDirectories())
folderList.Add(folder.Name);
}
catch (Exception ex)
{
throw new Exception(Lang.Text("Minecraft.Error.CannotReadInstanceFolder", versionsPath), ex);
}
// 如果没有可用实例,清空缓存并跳过后续处理
if (!folderList.Any())
{
ModBase.WriteIni(Path.Combine(path, "PCL.ini"), "InstanceCache", "");
McMcInstanceSelected = null;
States.Game.SelectedInstance = "";
ModBase.Log("[Minecraft] 未找到可用 Minecraft 实例");
return;
}
// 根据文件夹名列表生成辨识码
var folderListHash = ModBase.GetHash(mcInstanceCacheVersion + "#" + string.Join("#", folderList));
var folderListCheck = (int)(folderListHash % (int.MaxValue - 1));
// 尝试使用缓存
var useCache = !mcInstanceListForceRefresh &&
ModBase.Val(ModBase.ReadIni(Path.Combine(path, "PCL.ini"), "InstanceCache")) ==
folderListCheck;
if (useCache)
{
var cachedResult = InitMcInstanceListWithCache(path);
if (cachedResult is not null)
mcInstanceList = cachedResult;
else
useCache = false; // 缓存无效,需要重载
}
// 如果不能使用缓存,重新加载
if (!useCache)
{
mcInstanceListForceRefresh = false;
ModBase.Log("[Minecraft] 文件夹列表变更或缓存无效,重载所有实例");
ModBase.WriteIni(Path.Combine(path, "PCL.ini"), "InstanceCache", folderListCheck.ToString());
mcInstanceList = InitMcInstanceListWithoutCache(path);
}
_isFirstMcInstanceListLoad = false;
if (loader.IsAborted)
return;
// 尝试读取已储存的选择
var savedSelection = ModBase.ReadIni(Path.Combine(path, "PCL.ini"), "Version");
if (!string.IsNullOrEmpty(savedSelection))
foreach (var card in mcInstanceList)
foreach (var instance in card.Value)
if ((instance.Name ?? "") == savedSelection && instance.state != McInstanceState.Error)
{
McMcInstanceSelected = instance;
States.Game.SelectedInstance = McMcInstanceSelected.Name;
ModBase.Log("[Minecraft] 选择该文件夹储存的 Minecraft 实例:" + McMcInstanceSelected.PathInstance);
return;
}
// 自动选择第一项
var firstInstance = mcInstanceList
.SelectMany(kv => kv.Value)
.FirstOrDefault(i => i.state != McInstanceState.Error);
if (firstInstance is not null)
{
McMcInstanceSelected = firstInstance;
States.Game.SelectedInstance = McMcInstanceSelected.Name;
ModBase.Log("[Launch] 自动选择 Minecraft 实例:" + McMcInstanceSelected.PathInstance);
}
else
{
McMcInstanceSelected = null;
States.Game.SelectedInstance = "";
ModBase.Log("[Minecraft] 未找到可用 Minecraft 实例");
}
// 调试延迟
if (Config.Debug.AddRandomDelay is bool debugDelay && debugDelay)
Thread.Sleep(RandomUtils.NextInt(200, 3000));
}
catch (ThreadInterruptedException)
{
// 中断线程时什么也不做
}
catch (Exception ex)
{
ModBase.WriteIni(Path.Combine(path, "PCL.ini"), "InstanceCache", ""); // 要求下次重新加载
ModBase.Log(
ex,
Lang.Text("Select.Instance.Error.ListLoad"),
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Select.Instance.Error.ListLoad"));
}
}
// 获取实例列表
private static Dictionary> InitMcInstanceListWithCache(string path)
{
var results = new Dictionary>();
try
{
var cardCount = int.Parse(ModBase.ReadIni(path + "PCL.ini", "CardCount", (-1).ToString()));
if (cardCount == -1)
return null;
for (int i = 0, loopTo = cardCount - 1; i <= loopTo; i++)
{
var cardType =
(McInstanceCardType)int.Parse(ModBase.ReadIni(path + "PCL.ini", "CardKey" + (i + 1),
"0"));
var instanceList = new List();
// 循环读取实例
foreach (var folder in ModBase.ReadIni(path + "PCL.ini", "CardValue" + (i + 1), ":").Split(":"))
{
if (string.IsNullOrEmpty(folder))
continue;
var versionFolder = $@"{path}versions\{folder}\";
if (File.Exists(versionFolder + ".pclignore"))
{
if (_isFirstMcInstanceListLoad)
{
ModBase.Log("[Minecraft] 清理残留的忽略项目:" + versionFolder); // #2781
File.Delete(versionFolder + ".pclignore");
}
else
{
ModBase.Log("[Minecraft] 跳过要求忽略的项目:" + versionFolder);
continue;
}
}
try
{
// 读取单个实例
var instance = new PCL.McInstance(versionFolder);
instanceList.Add(instance);
var instanceCfg = States.Instance;
instance.Desc = instanceCfg.CustomInfo[instance.PathInstance];
if (string.IsNullOrEmpty(instance.Desc))
instance.Desc = instanceCfg.Info[instance.PathInstance];
if (!instanceCfg.LogoPathConfig.IsDefault(instance.PathInstance))
instance.Logo = instanceCfg.LogoPath[instance.PathInstance];
if (!instanceCfg.ReleaseTimeConfig.IsDefault(instance.PathInstance))
instance.releaseTime = DateTime.Parse(instanceCfg.ReleaseTime[instance.PathInstance]);
if (!instanceCfg.StateConfig.IsDefault(instance.PathInstance))
instance.state =
(McInstanceState)(int)instanceCfg.State[instance.PathInstance];
instance.IsStar = instanceCfg.Starred[instance.PathInstance];
instance.displayType =
(McInstanceCardType)(int)instanceCfg.CardType[instance.PathInstance];
if (instance.state != McInstanceState.Error &&
!instanceCfg.VanillaVersionNameConfig.IsDefault(instance.PathInstance) &&
!instanceCfg.VanillaVersionConfig
.IsDefault(instance.PathInstance)) // 旧版本可能没有这一项,导致 Instance 不加载(#643)
{
var instanceInfo = new McInstanceInfo
{
Fabric = instanceCfg.FabricVersion[instance.PathInstance],
LegacyFabric = instanceCfg.LegacyFabricVersion[instance.PathInstance],
Quilt = instanceCfg.QuiltVersion[instance.PathInstance],
Forge = instanceCfg.ForgeVersion[instance.PathInstance],
LabyMod = instanceCfg.LabyModVersion[instance.PathInstance],
NeoForge = instanceCfg.NeoForgeVersion[instance.PathInstance],
Cleanroom = instanceCfg.CleanroomVersion[instance.PathInstance],
OptiFine = instanceCfg.OptiFineVersion[instance.PathInstance],
HasLiteLoader = instanceCfg.HasLiteLoader[instance.PathInstance],
VanillaName = instanceCfg.VanillaVersionName[instance.PathInstance],
vanilla = new Version(instanceCfg.VanillaVersion[instance.PathInstance])
};
instanceInfo.HasFabric = instanceInfo.Fabric.Any();
instanceInfo.HasLegacyFabric = instanceInfo.LegacyFabric.Any();
instanceInfo.HasQuilt = instanceInfo.Quilt.Any();
instanceInfo.HasForge = instanceInfo.Forge.Any();
instanceInfo.HasNeoForge = instanceInfo.NeoForge.Any();
instanceInfo.HasCleanroom = instanceInfo.Cleanroom.Any();
instanceInfo.HasOptiFine = instanceInfo.OptiFine.Any();
instance.Info = instanceInfo;
}
// 重新检查错误实例
if (instance.state == McInstanceState.Error)
{
// 重新获取实例错误信息
var oldDesc = instance.Desc;
instance.state = McInstanceState.Original;
instance.Check();
// 校验错误原因是否改变
var customInfo = States.Instance.CustomInfo[instance.PathInstance];
if (instance.state == McInstanceState.Original || (string.IsNullOrEmpty(customInfo) &&
!((oldDesc ?? "") ==
(instance.Desc ?? ""))))
{
ModBase.Log("[Minecraft] 实例 " + instance.Name + " 的错误状态已变更,新的状态为:" + instance.Desc);
return null;
}
}
// 校验未加载的实例
if (string.IsNullOrEmpty(instance.Logo))
{
ModBase.Log("[Minecraft] 实例 " + instance.Name + " 未被加载");
return null;
}
}
catch (Exception ex)
{
ModBase.Log(ex, "读取实例加载缓存失败(" + folder + ")");
return null;
}
}
if (instanceList.Any())
results.Add(cardType, instanceList);
}
return results;
}
catch (Exception ex)
{
ModBase.Log(ex, "读取实例缓存失败");
return null;
}
}
private static Dictionary> InitMcInstanceListWithoutCache(string path)
{
var instanceList = new List();
#region 循环加载每个实例的信息
foreach (var folder in new DirectoryInfo(path + "versions").GetDirectories())
{
if (!folder.Exists || !folder.EnumerateFiles().Any())
{
ModBase.Log("[Minecraft] 跳过空文件夹:" + folder.FullName);
continue;
}
if ((folder.Name == "cache" || folder.Name == "BLClient" || folder.Name == "PCL") &&
!File.Exists(Path.Combine(folder.FullName, folder.Name + ".json")))
{
ModBase.Log("[Minecraft] 跳过可能不是实例文件夹的项目:" + folder.FullName);
continue;
}
var instanceFolder = folder.FullName + @"\";
if (File.Exists(instanceFolder + ".pclignore"))
{
if (_isFirstMcInstanceListLoad)
{
ModBase.Log("[Minecraft] 清理残留的忽略项目:" + instanceFolder); // #2781
try
{
File.Delete(instanceFolder + ".pclignore");
}
catch (Exception ex)
{
ModBase.Log(
ex,
Lang.Text("Select.Folder.Error.Cleanup", instanceFolder),
ModBase.LogLevel.Hint,
userSummary: Lang.Text("Select.Folder.Error.Cleanup", instanceFolder));
}
}
else
{
ModBase.Log("[Minecraft] 跳过要求忽略的项目:" + instanceFolder);
continue;
}
}
var instance = new PCL.McInstance(instanceFolder);
instanceList.Add(instance);
instance.Load();
}
#endregion
var results = new Dictionary>();
#region 将实例分类到各个卡片
try
{
// 未经过自定义的实例列表
var instanceListOriginal = new Dictionary>();
// 单独列出收藏的实例
var staredInstances = new List();
foreach (var instance in instanceList.ToList())
{
if (!instance.IsStar)
continue;
if (instance.displayType == McInstanceCardType.Hidden)
continue;
staredInstances.Add(instance);
instanceList.Remove(instance);
}
if (staredInstances.Any())
instanceListOriginal.Add(McInstanceCardType.Star, staredInstances);
// 预先筛选出愚人节和错误的实例
McInstanceFilter(ref instanceList, ref instanceListOriginal, new[] { McInstanceState.Error },
McInstanceCardType.Error);
McInstanceFilter(ref instanceList, ref instanceListOriginal, new[] { McInstanceState.Fool },
McInstanceCardType.Fool);
// 筛选 API 实例
McInstanceFilter(ref instanceList, ref instanceListOriginal,
new[]
{
McInstanceState.Forge, McInstanceState.NeoForge, McInstanceState.LiteLoader, McInstanceState.Fabric,
McInstanceState.LegacyFabric, McInstanceState.Quilt, McInstanceState.Cleanroom,
McInstanceState.LabyMod
}, McInstanceCardType.API);
// 将老实例预先分类入不常用,只剩余原版、快照、OptiFine
var instanceUseful = new List();
var instanceRubbish = new List();
McInstanceFilter(ref instanceList, new[] { McInstanceState.Old }, ref instanceRubbish);
// 确认最新实例,若为快照则加入常用列表
var latestInstance = instanceList
.Where(v => v.state == McInstanceState.Original || v.state == McInstanceState.Snapshot)
.MaxOrDefault(v => v.releaseTime);
if (latestInstance is not null && latestInstance.state == McInstanceState.Snapshot)
{
instanceUseful.Add(latestInstance);
instanceList.Remove(latestInstance);
}
// 将剩余的快照全部拖进不常用列表
McInstanceFilter(ref instanceList, new[] { McInstanceState.Snapshot }, ref instanceRubbish);
// 获取每个 Drop 下最新的原版与 OptiFine
var newerInstance = new Dictionary();
var existDrops = new List();
foreach (var instance in instanceList)
{
if (!instance.Info.Valid)
continue;
if (!existDrops.Contains(instance.Info.Drop))
existDrops.Add(instance.Info.Drop);
var key = instance.Info.Drop + "-" + (int)instance.state;
if (!newerInstance.ContainsKey(key))
{
newerInstance.Add(key, instance);
continue;
}
if (instance.Info.HasOptiFine)
{
if (instance.Info.OptiFineCode > newerInstance[key].Info.OptiFineCode)
newerInstance[key] = instance; // OptiFine 根据版本号判断
}
else if (instance.releaseTime > newerInstance[key].releaseTime)
{
newerInstance[key] = instance; // 原版根据发布时间判断
}
}
// 将每个 Drop 下的最常规版本加入
foreach (var drop in existDrops)
if (newerInstance.ContainsKey(drop + "-" + (int)McInstanceState.OptiFine) &&
newerInstance.ContainsKey(drop + "-" + (int)McInstanceState.Original))
{
// 同时存在 OptiFine 与原版
var vanillaInstance = newerInstance[drop + "-" + (int)McInstanceState.Original];
var optiFineInstance = newerInstance[drop + "-" + (int)McInstanceState.OptiFine];
if (vanillaInstance.Info.Drop > optiFineInstance.Info.Drop)
{
// 仅在原版比 OptiFine 更新时才加入原版
instanceUseful.Add(vanillaInstance);
instanceList.Remove(vanillaInstance);
}
instanceUseful.Add(optiFineInstance);
instanceList.Remove(optiFineInstance);
}
else if (newerInstance.ContainsKey(drop + "-" + (int)McInstanceState.OptiFine))
{
// 没有原版,直接加入 OptiFine
instanceUseful.Add(newerInstance[drop + "-" + (int)McInstanceState.OptiFine]);
instanceList.Remove(newerInstance[drop + "-" + (int)McInstanceState.OptiFine]);
}
else if (newerInstance.ContainsKey(drop + "-" + (int)McInstanceState.Original))
{
// 没有 OptiFine,直接加入原版
instanceUseful.Add(newerInstance[drop + "-" + (int)McInstanceState.Original]);
instanceList.Remove(newerInstance[drop + "-" + (int)McInstanceState.Original]);
}
// 将剩余的东西添加进去
instanceRubbish.AddRange(instanceList);
if (instanceUseful.Any())
instanceListOriginal.Add(McInstanceCardType.OriginalLike, instanceUseful);
if (instanceRubbish.Any())
instanceListOriginal.Add(McInstanceCardType.Rubbish, instanceRubbish);
// 按照自定义实例分类重新添加
foreach (var instancePair in instanceListOriginal)
foreach (var instance in instancePair.Value)
{
var realType = instance.displayType == 0 || instancePair.Key == McInstanceCardType.Star
? instancePair.Key
: instance.displayType;
if (!results.ContainsKey(realType))
results.Add(realType, new List());
results[realType].Add(instance);
}
}
catch (Exception ex)
{
results.Clear();
ModBase.Log(
ex,
Lang.Text("Select.Instance.Error.Classify"),
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Select.Instance.Error.Classify"));
}
#endregion
#region 对卡片与实例进行排序
// 卡片排序
var sortedInstanceList = new Dictionary>();
foreach (var sortRule in new[]
{
McInstanceCardType.Star, McInstanceCardType.API, McInstanceCardType.OriginalLike,
McInstanceCardType.Rubbish, McInstanceCardType.Fool, McInstanceCardType.Error,
McInstanceCardType.Hidden
})
if (results.ContainsKey(sortRule))
sortedInstanceList.Add(sortRule,
results[sortRule]);
results = sortedInstanceList;
// 版本排序
foreach (var cardType in new[]
{
McInstanceCardType.Star, McInstanceCardType.API, McInstanceCardType.OriginalLike,
McInstanceCardType.Rubbish, McInstanceCardType.Fool
})
{
if (!results.ContainsKey(cardType))
continue;
int getComponentCode(PCL.McInstance instance)
{
if (instance.Info.ForgelikeCode > 0)
return instance.Info.ForgelikeCode;
if (instance.Info.HasOptiFine)
return instance.Info.OptiFineCode;
return 0;
}
;
results[cardType] = SortUtils.Sort(results[cardType], (left, right) =>
{
// 发布时间
if ((left.releaseTime.Year >= 2000 || right.releaseTime.Year >= 2000) &&
left.releaseTime != right.releaseTime)
return left.releaseTime > right.releaseTime;
// 附加组件种类
if (left.Info.HasFabric != right.Info.HasFabric)
return left.Info.HasFabric;
if (left.Info.HasQuilt != right.Info.HasQuilt)
return left.Info.HasQuilt;
if (left.Info.HasLegacyFabric != right.Info.HasLegacyFabric)
return left.Info.HasLegacyFabric;
if (left.Info.HasNeoForge != right.Info.HasNeoForge)
return left.Info.HasNeoForge;
if (left.Info.HasForge != right.Info.HasForge)
return left.Info.HasForge;
if (left.Info.HasCleanroom != right.Info.HasCleanroom)
return left.Info.HasCleanroom;
if (left.Info.HasLabyMod != right.Info.HasLabyMod)
return left.Info.HasLabyMod;
if (left.Info.HasOptiFine != right.Info.HasOptiFine)
return left.Info.HasOptiFine;
if (left.Info.HasLiteLoader != right.Info.HasLiteLoader)
return left.Info.HasLiteLoader;
// 附加组件版本
if (getComponentCode(left) != getComponentCode(right))
return getComponentCode(left) > getComponentCode(right);
// 名称
return string.CompareOrdinal(left.Name, right.Name) > 0;
});
}
#endregion
#region 保存卡片缓存
ModBase.WriteIni(path + "PCL.ini", "CardCount", results.Count.ToString());
for (int i = 0, loopTo = results.Count - 1; i <= loopTo; i++)
{
ModBase.WriteIni(path + "PCL.ini", "CardKey" + (i + 1),
((int)results.Keys.ElementAtOrDefault(i)).ToString());
var value = "";
foreach (var Instance in results.Values.ElementAtOrDefault(i))
value += Instance.Name + ":";
ModBase.WriteIni(path + "PCL.ini", "CardValue" + (i + 1), value);
}
#endregion
return results;
}
///
/// 筛选特定种类的实例,并直接添加为卡片。
///
/// 用于筛选的列表。
/// 需要筛选出的实例类型。-2 代表隐藏的实例。
/// 卡片的名称。
private static void McInstanceFilter(ref List instanceList,
ref Dictionary> target, McInstanceState[] formula,
McInstanceCardType cardType)
{
var keepList = instanceList.Where(v => formula.Contains(v.state)).ToList();
// 加入实例列表,并从剩余中删除
if (keepList.Any())
{
target.Add(cardType, keepList);
instanceList = instanceList.Except(keepList).ToList();
}
}
///
/// 筛选特定种类的实例,并增加入一个已有列表中。
///
/// 用于筛选的列表。
/// 需要筛选出的实例类型。-2 代表隐藏的实例。
/// 传入需要增加入的列表。
private static void McInstanceFilter(ref List instanceList, McInstanceState[] formula,
ref List keepList)
{
keepList.AddRange(instanceList.Where(v => formula.Contains(v.state)));
// 加入实例列表,并从剩余中删除
if (keepList.Any()) instanceList = instanceList.Except(keepList).ToList();
}
#endregion
}