/// <summary>Get update keys based on the available mod metadata, while maintaining the precedence order.</summary> /// <param name="specifiedKeys">The specified update keys.</param> /// <param name="record">The mod's entry in SMAPI's internal database.</param> /// <param name="entry">The mod's entry in the wiki list.</param> private IEnumerable <UpdateKey> GetUpdateKeys(string[] specifiedKeys, ModDataRecord record, WikiModEntry entry) { // get unique update keys List <UpdateKey> updateKeys = this.GetUnfilteredUpdateKeys(specifiedKeys, record, entry) .Select(UpdateKey.Parse) .Distinct() .ToList(); // apply overrides from wiki if (entry?.Overrides?.ChangeUpdateKeys?.HasChanges == true) { List <string> newKeys = updateKeys.Select(p => p.ToString()).ToList(); entry.Overrides.ChangeUpdateKeys.Apply(newKeys); updateKeys = newKeys.Select(UpdateKey.Parse).ToList(); } // if the list has both an update key (like "Nexus:2400") and subkey (like "Nexus:2400@subkey") for the same page, the subkey takes priority { var removeKeys = new HashSet <UpdateKey>(); foreach (var key in updateKeys) { if (key.Subkey != null) { removeKeys.Add(new UpdateKey(key.Site, key.ID, null)); } } if (removeKeys.Any()) { updateKeys.RemoveAll(removeKeys.Contains); } } return(updateKeys); }
/// <summary>Construct an instance.</summary> /// <param name="wiki">The mod metadata from the wiki (if available).</param> /// <param name="db">The mod metadata from SMAPI's internal DB (if available).</param> public ModExtendedMetadataModel(WikiModEntry wiki, ModDataRecord db) { // wiki data if (wiki != null) { this.ID = wiki.ID; this.Name = wiki.Name.FirstOrDefault(); this.NexusID = wiki.NexusID; this.ChucklefishID = wiki.ChucklefishID; this.ModDropID = wiki.ModDropID; this.GitHubRepo = wiki.GitHubRepo; this.CustomSourceUrl = wiki.CustomSourceUrl; this.CustomUrl = wiki.CustomUrl; this.Smapi3Status = wiki.Smapi3Status; this.Smapi3Url = wiki.Smapi3Url; this.CompatibilityStatus = wiki.Compatibility.Status; this.CompatibilitySummary = wiki.Compatibility.Summary; this.BetaCompatibilityStatus = wiki.BetaCompatibility?.Status; this.BetaCompatibilitySummary = wiki.BetaCompatibility?.Summary; } // internal DB data if (db != null) { this.ID = this.ID.Union(db.FormerIDs).ToArray(); this.Name = this.Name ?? db.DisplayName; } }
/// <summary>Get update keys based on the available mod metadata, while maintaining the precedence order.</summary> /// <param name="specifiedKeys">The specified update keys.</param> /// <param name="record">The mod's entry in SMAPI's internal database.</param> /// <param name="entry">The mod's entry in the wiki list.</param> private IEnumerable <UpdateKey> GetUpdateKeys(string[] specifiedKeys, ModDataRecord record, WikiModEntry entry) { IEnumerable <string> GetRaw() { // specified update keys if (specifiedKeys != null) { foreach (string key in specifiedKeys) { yield return(key?.Trim()); } } // default update key string defaultKey = record?.GetDefaultUpdateKey(); if (defaultKey != null) { yield return(defaultKey); } // wiki metadata if (entry != null) { if (entry.NexusID.HasValue) { yield return($"{ModRepositoryKey.Nexus}:{entry.NexusID}"); } if (entry.ModDropID.HasValue) { yield return($"{ModRepositoryKey.ModDrop}:{entry.ModDropID}"); } if (entry.CurseForgeID.HasValue) { yield return($"{ModRepositoryKey.CurseForge}:{entry.CurseForgeID}"); } if (entry.ChucklefishID.HasValue) { yield return($"{ModRepositoryKey.Chucklefish}:{entry.ChucklefishID}"); } } } HashSet <UpdateKey> seen = new HashSet <UpdateKey>(); foreach (string rawKey in GetRaw()) { if (string.IsNullOrWhiteSpace(rawKey)) { continue; } UpdateKey key = UpdateKey.Parse(rawKey); if (seen.Add(key)) { yield return(key); } } }
/// <summary>Set up a mock mod metadata for <see cref="ModResolver.ValidateManifests"/>.</summary> /// <param name="mod">The mock mod metadata.</param> /// <param name="modRecord">The extra metadata about the mod from SMAPI's internal data (if any).</param> private void SetupMetadataForValidation(Mock <IModMetadata> mod, ModDataRecord modRecord = null) { mod.Setup(p => p.Status).Returns(ModMetadataStatus.Found); mod.Setup(p => p.DataRecord).Returns(() => null); mod.Setup(p => p.Manifest).Returns(this.GetManifest()); mod.Setup(p => p.DirectoryPath).Returns(Path.GetTempPath()); mod.Setup(p => p.DataRecord).Returns(modRecord); }
/********* ** Public methods *********/ /// <summary>Get manifest metadata for each folder in the given root path.</summary> /// <param name="rootPath">The root path to search for mods.</param> /// <param name="jsonHelper">The JSON helper with which to read manifests.</param> /// <param name="dataRecords">Metadata about mods from SMAPI's internal data.</param> /// <returns>Returns the manifests by relative folder.</returns> public IEnumerable <IModMetadata> ReadManifests(string rootPath, JsonHelper jsonHelper, IEnumerable <ModDataRecord> dataRecords) { dataRecords = dataRecords.ToArray(); foreach (DirectoryInfo modDir in this.GetModFolders(rootPath)) { // read file Manifest manifest = null; string path = Path.Combine(modDir.FullName, "manifest.json"); string error = null; try { // read manifest manifest = jsonHelper.ReadJsonFile <Manifest>(path); // validate if (manifest == null) { error = File.Exists(path) ? "its manifest is invalid." : "it doesn't have a manifest."; } else if (string.IsNullOrWhiteSpace(manifest.EntryDll)) { error = "its manifest doesn't set an entry DLL."; } } catch (SParseException ex) { error = $"parsing its manifest failed: {ex.Message}"; } catch (Exception ex) { error = $"parsing its manifest failed:\n{ex.GetLogSummary()}"; } // validate metadata ModDataRecord dataRecord = null; if (manifest != null) { // get unique key for lookups string key = !string.IsNullOrWhiteSpace(manifest.UniqueID) ? manifest.UniqueID : manifest.EntryDll; // get data record dataRecord = dataRecords.FirstOrDefault(record => record.ID.Matches(key, manifest)); } // build metadata string displayName = !string.IsNullOrWhiteSpace(manifest?.Name) ? manifest.Name : modDir.FullName.Replace(rootPath, "").Trim('/', '\\'); ModMetadataStatus status = error == null ? ModMetadataStatus.Found : ModMetadataStatus.Failed; yield return(new ModMetadata(displayName, modDir.FullName, manifest, dataRecord).SetStatus(status, error)); } }
/// <summary>Get every available update key based on the available mod metadata, including duplicates and keys which should be filtered.</summary> /// <param name="specifiedKeys">The specified update keys.</param> /// <param name="record">The mod's entry in SMAPI's internal database.</param> /// <param name="entry">The mod's entry in the wiki list.</param> private IEnumerable <string> GetUnfilteredUpdateKeys(string[] specifiedKeys, ModDataRecord record, WikiModEntry entry) { // specified update keys foreach (string key in specifiedKeys ?? Array.Empty <string>()) { if (!string.IsNullOrWhiteSpace(key)) { yield return(key.Trim()); } } // default update key { string defaultKey = record?.GetDefaultUpdateKey(); if (!string.IsNullOrWhiteSpace(defaultKey)) { yield return(defaultKey); } } // wiki metadata if (entry != null) { if (entry.NexusID.HasValue) { yield return(UpdateKey.GetString(ModSiteKey.Nexus, entry.NexusID.ToString())); } if (entry.ModDropID.HasValue) { yield return(UpdateKey.GetString(ModSiteKey.ModDrop, entry.ModDropID.ToString())); } if (entry.CurseForgeID.HasValue) { yield return(UpdateKey.GetString(ModSiteKey.CurseForge, entry.CurseForgeID.ToString())); } if (entry.ChucklefishID.HasValue) { yield return(UpdateKey.GetString(ModSiteKey.Chucklefish, entry.ChucklefishID.ToString())); } } // overrides from wiki foreach (string key in entry?.ChangeUpdateKeys ?? Array.Empty <string>()) { if (key.StartsWith('+')) { yield return(key.Substring(1)); } else if (!key.StartsWith("-")) { yield return(key); } } }
/// <summary>Get update keys based on the available mod metadata, while maintaining the precedence order.</summary> /// <param name="specifiedKeys">The specified update keys.</param> /// <param name="record">The mod's entry in SMAPI's internal database.</param> /// <param name="entry">The mod's entry in the wiki list.</param> public IEnumerable <string> GetUpdateKeys(string[] specifiedKeys, ModDataRecord record, WikiModEntry entry) { IEnumerable <string> GetRaw() { // specified update keys if (specifiedKeys != null) { foreach (string key in specifiedKeys) { yield return(key?.Trim()); } } // default update key string defaultKey = record?.GetDefaultUpdateKey(); if (defaultKey != null) { yield return(defaultKey); } // wiki metadata if (entry != null) { if (entry.NexusID.HasValue) { yield return($"Nexus:{entry.NexusID}"); } if (entry.ChucklefishID.HasValue) { yield return($"Chucklefish:{entry.ChucklefishID}"); } if (entry.ModDropID.HasValue) { yield return($"ModDrop:{entry.ModDropID}"); } } } HashSet <string> seen = new HashSet <string>(StringComparer.InvariantCulture); foreach (string key in GetRaw()) { if (!string.IsNullOrWhiteSpace(key) && seen.Add(key)) { yield return(key); } } }
/// <summary>Construct an instance.</summary> /// <param name="wiki">The mod metadata from the wiki (if available).</param> /// <param name="db">The mod metadata from SMAPI's internal DB (if available).</param> /// <param name="main">The main version.</param> /// <param name="optional">The latest optional version, if newer than <paramref name="main"/>.</param> /// <param name="unofficial">The latest unofficial version, if newer than <paramref name="main"/> and <paramref name="optional"/>.</param> /// <param name="unofficialForBeta">The latest unofficial version for the current Stardew Valley or SMAPI beta, if any.</param> public ModExtendedMetadataModel(WikiModEntry wiki, ModDataRecord db, ModEntryVersionModel main, ModEntryVersionModel optional, ModEntryVersionModel unofficial, ModEntryVersionModel unofficialForBeta) { // versions this.Main = main; this.Optional = optional; this.Unofficial = unofficial; this.UnofficialForBeta = unofficialForBeta; // wiki data if (wiki != null) { this.ID = wiki.ID; this.Name = wiki.Name.FirstOrDefault(); this.NexusID = wiki.NexusID; this.ChucklefishID = wiki.ChucklefishID; this.CurseForgeID = wiki.CurseForgeID; this.CurseForgeKey = wiki.CurseForgeKey; this.ModDropID = wiki.ModDropID; this.GitHubRepo = wiki.GitHubRepo; this.CustomSourceUrl = wiki.CustomSourceUrl; this.CustomUrl = wiki.CustomUrl; this.CompatibilityStatus = wiki.Compatibility.Status; this.CompatibilitySummary = wiki.Compatibility.Summary; this.BrokeIn = wiki.Compatibility.BrokeIn; this.BetaCompatibilityStatus = wiki.BetaCompatibility?.Status; this.BetaCompatibilitySummary = wiki.BetaCompatibility?.Summary; this.BetaBrokeIn = wiki.BetaCompatibility?.BrokeIn; this.ChangeLocalVersions = wiki.Overrides?.ChangeLocalVersions?.ToString(); this.ChangeRemoteVersions = wiki.Overrides?.ChangeRemoteVersions?.ToString(); this.ChangeUpdateKeys = wiki.Overrides?.ChangeUpdateKeys?.ToString(); } // internal DB data if (db != null) { this.ID = this.ID.Union(db.FormerIDs).ToArray(); this.Name ??= db.DisplayName; } }
/// <summary>Get update keys based on the available mod metadata, while maintaining the precedence order.</summary> /// <param name="specifiedKeys">The specified update keys.</param> /// <param name="record">The mod's entry in SMAPI's internal database.</param> /// <param name="entry">The mod's entry in the wiki list.</param> private IEnumerable <UpdateKey> GetUpdateKeys(string[] specifiedKeys, ModDataRecord record, WikiModEntry entry) { // get unique update keys List <UpdateKey> updateKeys = this.GetUnfilteredUpdateKeys(specifiedKeys, record, entry) .Select(UpdateKey.Parse) .Distinct() .ToList(); // apply remove overrides from wiki { var removeKeys = new HashSet <UpdateKey>( from key in entry?.ChangeUpdateKeys ?? new string[0] where key.StartsWith('-') select UpdateKey.Parse(key.Substring(1)) ); if (removeKeys.Any()) { updateKeys.RemoveAll(removeKeys.Contains); } } // if the list has both an update key (like "Nexus:2400") and subkey (like "Nexus:2400@subkey") for the same page, the subkey takes priority { var removeKeys = new HashSet <UpdateKey>(); foreach (var key in updateKeys) { if (key.Subkey != null) { removeKeys.Add(new UpdateKey(key.Site, key.ID, null)); } } if (removeKeys.Any()) { updateKeys.RemoveAll(removeKeys.Contains); } } return(updateKeys); }
/********* ** Private methods *********/ /// <summary>Get the metadata for a mod.</summary> /// <param name="search">The mod data to match.</param> /// <param name="wikiData">The wiki data.</param> /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param> /// <returns>Returns the mod data if found, else <c>null</c>.</returns> private async Task <ModEntryModel> GetModData(ModSearchEntryModel search, WikiCompatibilityEntry[] wikiData, bool includeExtendedMetadata) { // resolve update keys var updateKeys = new HashSet <string>(search.UpdateKeys ?? new string[0], StringComparer.InvariantCultureIgnoreCase); ModDataRecord record = this.ModDatabase.Get(search.ID); if (record?.Fields != null) { string defaultUpdateKey = record.Fields.FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey && p.IsDefault)?.Value; if (!string.IsNullOrWhiteSpace(defaultUpdateKey)) { updateKeys.Add(defaultUpdateKey); } } // get latest versions ModEntryModel result = new ModEntryModel { ID = search.ID }; IList <string> errors = new List <string>(); foreach (string updateKey in updateKeys) { // fetch data ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey); if (data.Error != null) { errors.Add(data.Error); continue; } // handle main version if (data.Version != null) { if (!SemanticVersion.TryParse(data.Version, out ISemanticVersion version)) { errors.Add($"The update key '{updateKey}' matches a mod with invalid semantic version '{data.Version}'."); continue; } if (this.IsNewer(version, result.Main?.Version)) { result.Main = new ModEntryVersionModel(version, data.Url); } } // handle optional version if (data.PreviewVersion != null) { if (!SemanticVersion.TryParse(data.PreviewVersion, out ISemanticVersion version)) { errors.Add($"The update key '{updateKey}' matches a mod with invalid optional semantic version '{data.PreviewVersion}'."); continue; } if (this.IsNewer(version, result.Optional?.Version)) { result.Optional = new ModEntryVersionModel(version, data.Url); } } } // get unofficial version WikiCompatibilityEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(result.ID.Trim(), StringComparer.InvariantCultureIgnoreCase)); if (wikiEntry?.UnofficialVersion != null && this.IsNewer(wikiEntry.UnofficialVersion, result.Main?.Version) && this.IsNewer(wikiEntry.UnofficialVersion, result.Optional?.Version)) { result.Unofficial = new ModEntryVersionModel(wikiEntry.UnofficialVersion, this.WikiCompatibilityPageUrl); } // fallback to preview if latest is invalid if (result.Main == null && result.Optional != null) { result.Main = result.Optional; result.Optional = null; } // special cases if (result.ID == "Pathoschild.SMAPI") { if (result.Main != null) { result.Main.Url = "https://smapi.io/"; } if (result.Optional != null) { result.Optional.Url = "https://smapi.io/"; } } // add extended metadata if (includeExtendedMetadata && (wikiEntry != null || record != null)) { result.Metadata = new ModExtendedMetadataModel(wikiEntry, record); } // add result result.Errors = errors.ToArray(); return(result); }
/********* ** Private methods *********/ /// <summary>Get the metadata for a mod.</summary> /// <param name="search">The mod data to match.</param> /// <param name="wikiData">The wiki data.</param> /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param> /// <param name="apiVersion">The SMAPI version installed by the player.</param> /// <returns>Returns the mod data if found, else <c>null</c>.</returns> private async Task <ModEntryModel> GetModData(ModSearchEntryModel search, WikiModEntry[] wikiData, bool includeExtendedMetadata, ISemanticVersion apiVersion) { // cross-reference data ModDataRecord record = this.ModDatabase.Get(search.ID); WikiModEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(search.ID.Trim(), StringComparer.OrdinalIgnoreCase)); UpdateKey[] updateKeys = this.GetUpdateKeys(search.UpdateKeys, record, wikiEntry).ToArray(); ModOverrideConfig overrides = this.Config.Value.ModOverrides.FirstOrDefault(p => p.ID.Equals(search.ID?.Trim(), StringComparison.OrdinalIgnoreCase)); bool allowNonStandardVersions = overrides?.AllowNonStandardVersions ?? false; // get latest versions ModEntryModel result = new ModEntryModel { ID = search.ID }; IList <string> errors = new List <string>(); ModEntryVersionModel main = null; ModEntryVersionModel optional = null; ModEntryVersionModel unofficial = null; ModEntryVersionModel unofficialForBeta = null; foreach (UpdateKey updateKey in updateKeys) { // validate update key if (!updateKey.LooksValid) { errors.Add($"The update key '{updateKey}' isn't in a valid format. It should contain the site key and mod ID like 'Nexus:541', with an optional subkey like 'Nexus:541@subkey'."); continue; } // fetch data ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey, allowNonStandardVersions, wikiEntry?.MapRemoteVersions); if (data.Status != RemoteModStatus.Ok) { errors.Add(data.Error ?? data.Status.ToString()); continue; } // handle versions if (this.IsNewer(data.Version, main?.Version)) { main = new ModEntryVersionModel(data.Version, data.Url); } if (this.IsNewer(data.PreviewVersion, optional?.Version)) { optional = new ModEntryVersionModel(data.PreviewVersion, data.Url); } } // get unofficial version if (wikiEntry?.Compatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, main?.Version) && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, optional?.Version)) { unofficial = new ModEntryVersionModel(wikiEntry.Compatibility.UnofficialVersion, $"{this.Url.PlainAction("Index", "Mods", absoluteUrl: true)}#{wikiEntry.Anchor}"); } // get unofficial version for beta if (wikiEntry?.HasBetaInfo == true) { if (wikiEntry.BetaCompatibility.Status == WikiCompatibilityStatus.Unofficial) { if (wikiEntry.BetaCompatibility.UnofficialVersion != null) { unofficialForBeta = (wikiEntry.BetaCompatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, main?.Version) && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, optional?.Version)) ? new ModEntryVersionModel(wikiEntry.BetaCompatibility.UnofficialVersion, $"{this.Url.PlainAction("Index", "Mods", absoluteUrl: true)}#{wikiEntry.Anchor}") : null; } else { unofficialForBeta = unofficial; } } } // fallback to preview if latest is invalid if (main == null && optional != null) { main = optional; optional = null; } // special cases if (overrides?.SetUrl != null) { if (main != null) { main.Url = overrides.SetUrl; } if (optional != null) { optional.Url = overrides.SetUrl; } } // get recommended update (if any) ISemanticVersion installedVersion = this.ModSites.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions, allowNonStandard: allowNonStandardVersions); if (apiVersion != null && installedVersion != null) { // get newer versions List <ModEntryVersionModel> updates = new List <ModEntryVersionModel>(); if (this.IsRecommendedUpdate(installedVersion, main?.Version, useBetaChannel: true)) { updates.Add(main); } if (this.IsRecommendedUpdate(installedVersion, optional?.Version, useBetaChannel: installedVersion.IsPrerelease() || search.IsBroken)) { updates.Add(optional); } if (this.IsRecommendedUpdate(installedVersion, unofficial?.Version, useBetaChannel: true)) { updates.Add(unofficial); } if (this.IsRecommendedUpdate(installedVersion, unofficialForBeta?.Version, useBetaChannel: apiVersion.IsPrerelease())) { updates.Add(unofficialForBeta); } // get newest version ModEntryVersionModel newest = null; foreach (ModEntryVersionModel update in updates) { if (newest == null || update.Version.IsNewerThan(newest.Version)) { newest = update; } } // set field result.SuggestedUpdate = newest != null ? new ModEntryVersionModel(newest.Version, newest.Url) : null; } // add extended metadata if (includeExtendedMetadata) { result.Metadata = new ModExtendedMetadataModel(wikiEntry, record, main: main, optional: optional, unofficial: unofficial, unofficialForBeta: unofficialForBeta); } // add result result.Errors = errors.ToArray(); return(result); }
/********* ** Private methods *********/ /// <summary>Get the metadata for a mod.</summary> /// <param name="search">The mod data to match.</param> /// <param name="wikiData">The wiki data.</param> /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param> /// <returns>Returns the mod data if found, else <c>null</c>.</returns> private async Task <ModEntryModel> GetModData(ModSearchEntryModel search, WikiModEntry[] wikiData, bool includeExtendedMetadata) { // cross-reference data ModDataRecord record = this.ModDatabase.Get(search.ID); WikiModEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(search.ID.Trim(), StringComparer.InvariantCultureIgnoreCase)); UpdateKey[] updateKeys = this.GetUpdateKeys(search.UpdateKeys, record, wikiEntry).ToArray(); // get latest versions ModEntryModel result = new ModEntryModel { ID = search.ID }; IList <string> errors = new List <string>(); foreach (UpdateKey updateKey in updateKeys) { // validate update key if (!updateKey.LooksValid) { errors.Add($"The update key '{updateKey}' isn't in a valid format. It should contain the site key and mod ID like 'Nexus:541'."); continue; } // fetch data ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey); if (data.Error != null) { errors.Add(data.Error); continue; } // handle main version if (data.Version != null) { if (!SemanticVersion.TryParse(data.Version, out ISemanticVersion version)) { errors.Add($"The update key '{updateKey}' matches a mod with invalid semantic version '{data.Version}'."); continue; } if (this.IsNewer(version, result.Main?.Version)) { result.Main = new ModEntryVersionModel(version, data.Url); } } // handle optional version if (data.PreviewVersion != null) { if (!SemanticVersion.TryParse(data.PreviewVersion, out ISemanticVersion version)) { errors.Add($"The update key '{updateKey}' matches a mod with invalid optional semantic version '{data.PreviewVersion}'."); continue; } if (this.IsNewer(version, result.Optional?.Version)) { result.Optional = new ModEntryVersionModel(version, data.Url); } } } // get unofficial version if (wikiEntry?.Compatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, result.Main?.Version) && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, result.Optional?.Version)) { result.Unofficial = new ModEntryVersionModel(wikiEntry.Compatibility.UnofficialVersion, $"{this.CompatibilityPageUrl}/#{wikiEntry.Anchor}"); } // get unofficial version for beta if (wikiEntry?.HasBetaInfo == true) { result.HasBetaInfo = true; if (wikiEntry.BetaCompatibility.Status == WikiCompatibilityStatus.Unofficial) { if (wikiEntry.BetaCompatibility.UnofficialVersion != null) { result.UnofficialForBeta = (wikiEntry.BetaCompatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, result.Main?.Version) && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, result.Optional?.Version)) ? new ModEntryVersionModel(wikiEntry.BetaCompatibility.UnofficialVersion, $"{this.CompatibilityPageUrl}/#{wikiEntry.Anchor}") : null; } else { result.UnofficialForBeta = result.Unofficial; } } } // fallback to preview if latest is invalid if (result.Main == null && result.Optional != null) { result.Main = result.Optional; result.Optional = null; } // special cases if (result.ID == "Pathoschild.SMAPI") { if (result.Main != null) { result.Main.Url = "https://smapi.io/"; } if (result.Optional != null) { result.Optional.Url = "https://smapi.io/"; } } // add extended metadata if (includeExtendedMetadata && (wikiEntry != null || record != null)) { result.Metadata = new ModExtendedMetadataModel(wikiEntry, record); } // add result result.Errors = errors.ToArray(); return(result); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="displayName">The mod's display name.</param> /// <param name="directoryPath">The mod's full directory path.</param> /// <param name="manifest">The mod manifest.</param> /// <param name="dataRecord">Metadata about the mod from SMAPI's internal data (if any).</param> public ModMetadata(string displayName, string directoryPath, IManifest manifest, ModDataRecord dataRecord) { this.DisplayName = displayName; this.DirectoryPath = directoryPath; this.Manifest = manifest; this.DataRecord = dataRecord; }