using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace StardewModdingAPI.Framework.ModData { /// Raw mod metadata from SMAPI's internal mod list. internal class ModDataRecord { /********* ** Properties *********/ /// This field stores properties that aren't mapped to another field before they're parsed into . [JsonExtensionData] private IDictionary ExtensionData; /********* ** Accessors *********/ /// The mod's current unique ID. public string ID { get; set; } /// The former mod IDs (if any). /// /// This uses a custom format which uniquely identifies a mod across multiple versions and /// supports matching other fields if no ID was specified. This doesn't include the latest /// ID, if any. Format rules: /// 1. If the mod's ID changed over time, multiple variants can be separated by the /// | character. /// 2. Each variant can take one of two forms: /// - A simple string matching the mod's UniqueID value. /// - A JSON structure containing any of four manifest fields (ID, Name, Author, and /// EntryDll) to match. /// public string FormerIDs { get; set; } /// Maps local versions to a semantic version for update checks. public IDictionary MapLocalVersions { get; set; } = new Dictionary(); /// Maps remote versions to a semantic version for update checks. public IDictionary MapRemoteVersions { get; set; } = new Dictionary(); /// The versioned field data. /// /// This maps field names to values. This should be accessed via . /// Format notes: /// - Each key consists of a field name prefixed with any combination of version range /// and Default, separated by pipes (whitespace trimmed). For example, Name /// will always override the name, Default | Name will only override a blank /// name, and ~1.1 | Default | Name will override blank names up to version 1.1. /// - The version format is min~max (where either side can be blank for unbounded), or /// a single version number. /// - The field name itself corresponds to a value. /// public IDictionary Fields { get; set; } = new Dictionary(); /********* ** Public methods *********/ /// Get a parsed representation of the . public IEnumerable GetFields() { foreach (KeyValuePair pair in this.Fields) { // init fields string packedKey = pair.Key; string value = pair.Value; bool isDefault = false; ISemanticVersion lowerVersion = null; ISemanticVersion upperVersion = null; // parse string[] parts = packedKey.Split('|').Select(p => p.Trim()).ToArray(); ModDataFieldKey fieldKey = (ModDataFieldKey)Enum.Parse(typeof(ModDataFieldKey), parts.Last(), ignoreCase: true); foreach (string part in parts.Take(parts.Length - 1)) { // 'default' if (part.Equals("Default", StringComparison.InvariantCultureIgnoreCase)) { isDefault = true; continue; } // version range if (part.Contains("~")) { string[] versionParts = part.Split(new[] { '~' }, 2); lowerVersion = versionParts[0] != "" ? new SemanticVersion(versionParts[0]) : null; upperVersion = versionParts[1] != "" ? new SemanticVersion(versionParts[1]) : null; continue; } // single version lowerVersion = new SemanticVersion(part); upperVersion = new SemanticVersion(part); } yield return new ModDataField(fieldKey, value, isDefault, lowerVersion, upperVersion); } } /// Get a semantic local version for update checks. /// The remote version to normalise. public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version) { return this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version.ToString(), out string newVersion) ? new SemanticVersion(newVersion) : version; } /// Get a semantic remote version for update checks. /// The remote version to normalise. public string GetRemoteVersionForUpdateChecks(string version) { // normalise version if possible if (SemanticVersion.TryParse(version, out ISemanticVersion parsed)) version = parsed.ToString(); // fetch remote version return this.MapRemoteVersions != null && this.MapRemoteVersions.TryGetValue(version, out string newVersion) ? newVersion : version; } /********* ** Private methods *********/ /// The method invoked after JSON deserialisation. /// The deserialisation context. [OnDeserialized] private void OnDeserialized(StreamingContext context) { if (this.ExtensionData != null) { this.Fields = this.ExtensionData.ToDictionary(p => p.Key, p => p.Value.ToString()); this.ExtensionData = null; } } } }