namespace PCLCS;
///
/// MC 版本安装清单及 PCL 管理文件清单。
///
/// 清单格式版本;无版本的旧清单为 0。
/// 安装目标及受管理组件版本。
/// 最后修改时间(UTC)。
/// 受管理文件;没有文件时为空列表。
public record McInstallManifest(int SchemaVersion, McComponents Components, DateTime LastModifiedUtc, IReadOnlyList ManagedFiles) {
///
/// 清理以 Mod 形式安装的、已删除或修改的组件的记录,并更新清单。
///
public McInstallManifest RefreshModComponents(string gameFolder) {
List invalidFiles = [];
foreach (ManagedFile entry in ManagedFiles) {
if (entry.Component is null) continue; // 不检查非组件文件
FileInfo file = new(Path.Combine(gameFolder, entry.RelativePath));
if (!file.Exists || file.LastWriteTimeUtc != entry.LastWriteTimeUtc.ToUniversalTime()) invalidFiles.Add(entry);
}
if (!invalidFiles.Any()) return this;
return this with {
Components = Components with {
OptiFineEntry = invalidFiles.Any(file => file.Component == "OptiFine") ? null : Components.OptiFineEntry,
FabricApiVersion = invalidFiles.Any(file => file.Component == "FabricApi") ? null : Components.FabricApiVersion,
OptiFabricVersion = invalidFiles.Any(file => file.Component == "OptiFabric") ? null : Components.OptiFabricVersion
},
ManagedFiles = ManagedFiles.Where(file => !invalidFiles.Contains(file)).ToList(),
LastModifiedUtc = DateTime.UtcNow
};
}
///
/// 由 PCL 管理并记录的版本内文件。
///
/// 相对于游戏目录的文件路径。确保路径为全小写且可比较。
/// 小写的 SHA1。
/// 目标文件的最后修改时间(UTC)。
/// 安装器组件名;为空时表示文件由整合包管理。
public record ManagedFile(string RelativePath, string Sha1, DateTime LastWriteTimeUtc,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? Component = null);
}
/// 目标 Minecraft 版本。
/// 目标中各个 Mod Loader 的版本;原版为空字典。
/// OptiFine 条目信息,不安装时为 。
/// 由 PCL 管理的 Fabric API 版本;不安装时为 。
/// 由 PCL 管理的 OptiFabric 版本;不安装时为 。
[method: JsonConstructor]
public record McComponents(string MinecraftVersion, Dictionary ModLoaderVersions,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] JObject? OptiFineEntry = null,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? FabricApiVersion = null,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? OptiFabricVersion = null) {
[JsonIgnore]
public string? OptiFineVersion => OptiFineEntry?["InstanceName"]?.Value()?.AfterLast("-OptiFine_");
public McComponents(string minecraftVersion, JObject? optiFineEntry, string? forgeVersion,
string? neoForgeVersion, string? fabricVersion, string? fabricApiVersion, string? optiFabricVersion, string? liteLoaderVersion)
: this(minecraftVersion, [], optiFineEntry, fabricApiVersion, optiFabricVersion) {
if (forgeVersion is not null) ModLoaderVersions[ModLoaders.Forge] = forgeVersion;
if (neoForgeVersion is not null) ModLoaderVersions[ModLoaders.NeoForge] = neoForgeVersion;
if (fabricVersion is not null) ModLoaderVersions[ModLoaders.Fabric] = fabricVersion;
if (liteLoaderVersion is not null) ModLoaderVersions[ModLoaders.LiteLoader] = liteLoaderVersion;
}
public bool IsEquivalentTo(McComponents other) =>
MinecraftVersion == other.MinecraftVersion &&
OptiFineVersion == other.OptiFineVersion &&
FabricApiVersion == other.FabricApiVersion &&
OptiFabricVersion == other.OptiFabricVersion &&
ModLoaderVersions.Count == other.ModLoaderVersions.Count &&
ModLoaderVersions.All(entry => other.ModLoaderVersions.TryGetValue(entry.Key, out string? version) && version == entry.Value);
}