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); }