Exemple #1
0
        /*********
        ** Private methods
        *********/
        /// <summary>Get the mod version numbers for the given mod.</summary>
        /// <param name="mod">The mod to check.</param>
        /// <param name="subkey">The optional update subkey to match in available files. (If no file names or descriptions contain the subkey, it'll be ignored.)</param>
        /// <param name="allowNonStandardVersions">Whether to allow non-standard versions.</param>
        /// <param name="mapRemoteVersions">Maps remote versions to a semantic version for update checks.</param>
        /// <param name="main">The main mod version.</param>
        /// <param name="preview">The latest prerelease version, if newer than <paramref name="main"/>.</param>
        private bool TryGetLatestVersions(IModPage mod, string subkey, bool allowNonStandardVersions, IDictionary <string, string> mapRemoteVersions, out ISemanticVersion main, out ISemanticVersion preview)
        {
            main    = null;
            preview = null;

            ISemanticVersion ParseVersion(string raw)
            {
                raw = this.NormalizeVersion(raw);
                return(this.GetMappedVersion(raw, mapRemoteVersions, allowNonStandardVersions));
            }

            if (mod != null)
            {
                // get mod version
                if (subkey == null)
                {
                    main = ParseVersion(mod.Version);
                }

                // get file versions
                foreach (IModDownload download in mod.Downloads)
                {
                    // check for subkey if specified
                    if (subkey != null && download.Name?.Contains(subkey, StringComparison.OrdinalIgnoreCase) != true && download.Description?.Contains(subkey, StringComparison.OrdinalIgnoreCase) != true)
                    {
                        continue;
                    }

                    // parse version
                    ISemanticVersion cur = ParseVersion(download.Version);
                    if (cur == null)
                    {
                        continue;
                    }

                    // track highest versions
                    if (main == null || cur.IsNewerThan(main))
                    {
                        main = cur;
                    }
                    if (cur.IsPrerelease() && (preview == null || cur.IsNewerThan(preview)))
                    {
                        preview = cur;
                    }
                }

                if (preview != null && !preview.IsNewerThan(main))
                {
                    preview = null;
                }
            }

            return(main != null);
        }
 /*********
 ** Public methods
 *********/
 /// <summary>Get a semantic local version for update checks.</summary>
 /// <param name="version">The remote version to normalise.</param>
 public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version)
 {
     return(this.DataRecord.GetLocalVersionForUpdateChecks(version));
 }
Exemple #3
0
 /// <summary>Construct an instance.</summary>
 /// <param name="name">The mod name.</param>
 /// <param name="version">The semantic version for the mod's latest release.</param>
 /// <param name="previewVersion">The semantic version for the mod's latest preview release, if available and different from <see cref="Version"/>.</param>
 /// <param name="url">The mod's web URL.</param>
 public ModInfoModel(string name, ISemanticVersion version, string url, ISemanticVersion previewVersion = null)
 {
     this
     .SetBasicInfo(name, url)
     .SetVersions(version, previewVersion);
 }
Exemple #4
0
        // Constructors:
        #region Constructors

        internal CompatibilityPatch(String uid, ISemanticVersion version)
        {
            this.uid     = uid;
            this.version = version;
        }
Exemple #5
0
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 /// <param name="baseUrl">The base URL for the web API.</param>
 /// <param name="version">The web API version.</param>
 public WebApiClient(string baseUrl, ISemanticVersion version)
 {
     this.BaseUrl = new Uri(baseUrl);
     this.Version = version;
 }
Exemple #6
0
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 /// <param name="mod">The mod metadata.</param>
 public MultiplayerPeerMod(RemoteContextModModel mod)
 {
     this.Name    = mod.Name;
     this.ID      = mod.ID?.Trim();
     this.Version = mod.Version;
 }
Exemple #7
0
        /*********
        ** Private methods
        *********/
        /// <summary>Parse a raw config schema for a content pack.</summary>
        /// <param name="rawSchema">The raw config schema.</param>
        /// <param name="logWarning">The callback to invoke on each validation warning, passed the field name and reason respectively.</param>
        /// <param name="formatVersion">The content format version.</param>
        private InvariantDictionary <ConfigField> LoadConfigSchema(InvariantDictionary <ConfigSchemaFieldConfig> rawSchema, Action <string, string> logWarning, ISemanticVersion formatVersion)
        {
            InvariantDictionary <ConfigField> schema = new InvariantDictionary <ConfigField>();

            if (rawSchema == null || !rawSchema.Any())
            {
                return(schema);
            }

            foreach (string rawKey in rawSchema.Keys)
            {
                ConfigSchemaFieldConfig field = rawSchema[rawKey];

                // validate format
                if (string.IsNullOrWhiteSpace(rawKey))
                {
                    logWarning(rawKey, "the config field name can't be empty.");
                    continue;
                }
                if (rawKey.Contains(InternalConstants.PositionalInputArgSeparator) || rawKey.Contains(InternalConstants.NamedInputArgSeparator))
                {
                    logWarning(rawKey, $"the name '{rawKey}' can't have input arguments ({InternalConstants.PositionalInputArgSeparator} or {InternalConstants.NamedInputArgSeparator} character).");
                    continue;
                }

                // validate reserved keys
                if (Enum.TryParse <ConditionType>(rawKey, true, out _))
                {
                    logWarning(rawKey, $"can't use {rawKey} as a config field, because it's a reserved condition key.");
                    continue;
                }

                // read allowed/default values
                InvariantHashSet allowValues   = this.ParseCommaDelimitedField(field.AllowValues);
                InvariantHashSet defaultValues = this.ParseCommaDelimitedField(field.Default);

                // pre-1.7 behaviour
                if (formatVersion.IsOlderThan("1.7"))
                {
                    // allowed values are required
                    if (!allowValues.Any())
                    {
                        logWarning(rawKey, $"no {nameof(ConfigSchemaFieldConfig.AllowValues)} specified (and format version is less than 1.7).");
                        continue;
                    }

                    // inject default if needed
                    if (!defaultValues.Any() && !field.AllowBlank)
                    {
                        defaultValues = new InvariantHashSet(allowValues.First());
                    }
                }

                // validate allowed values
                if (!field.AllowBlank && !defaultValues.Any())
                {
                    logWarning(rawKey, $"if {nameof(field.AllowBlank)} is false, you must specify {nameof(field.Default)}.");
                    continue;
                }
                if (allowValues.Any() && defaultValues.Any())
                {
                    string[] invalidValues = defaultValues.ExceptIgnoreCase(allowValues).ToArray();
                    if (invalidValues.Any())
                    {
                        logWarning(rawKey, $"default values '{string.Join(", ", invalidValues)}' are not allowed according to {nameof(ConfigSchemaFieldConfig.AllowValues)}.");
                        continue;
                    }
                }

                // validate allow multiple
                if (!field.AllowMultiple && defaultValues.Count > 1)
                {
                    logWarning(rawKey, $"can't have multiple default values because {nameof(ConfigSchemaFieldConfig.AllowMultiple)} is false.");
                    continue;
                }

                // add to schema
                schema[rawKey] = new ConfigField(allowValues, defaultValues, field.AllowBlank, field.AllowMultiple);
            }

            return(schema);
        }
Exemple #8
0
        /*********
        ** Private methods
        *********/
        /// <summary>Get the mod version numbers for the given mod.</summary>
        /// <param name="mod">The mod to check.</param>
        /// <param name="subkey">The optional update subkey to match in available files. (If no file names or descriptions contain the subkey, it'll be ignored.)</param>
        /// <param name="allowNonStandardVersions">Whether to allow non-standard versions.</param>
        /// <param name="mapRemoteVersions">The changes to apply to remote versions for update checks.</param>
        /// <param name="main">The main mod version.</param>
        /// <param name="preview">The latest prerelease version, if newer than <paramref name="main"/>.</param>
        private bool TryGetLatestVersions(IModPage mod, string subkey, bool allowNonStandardVersions, ChangeDescriptor mapRemoteVersions, out ISemanticVersion main, out ISemanticVersion preview)
        {
            main    = null;
            preview = null;

            // parse all versions from the mod page
            IEnumerable <(string name, string description, ISemanticVersion version)> GetAllVersions()
            {
                if (mod != null)
                {
                    ISemanticVersion ParseAndMapVersion(string raw)
                    {
                        raw = this.NormalizeVersion(raw);
                        return(this.GetMappedVersion(raw, mapRemoteVersions, allowNonStandardVersions));
                    }

                    // get mod version
                    ISemanticVersion modVersion = ParseAndMapVersion(mod.Version);
                    if (modVersion != null)
                    {
                        yield return(name : null, description : null, version : ParseAndMapVersion(mod.Version));
                    }

                    // get file versions
                    foreach (IModDownload download in mod.Downloads)
                    {
                        ISemanticVersion cur = ParseAndMapVersion(download.Version);
                        if (cur != null)
                        {
                            yield return(download.Name, download.Description, cur);
                        }
                    }
                }
            }

            var versions = GetAllVersions()
                           .OrderByDescending(p => p.version, SemanticVersionComparer.Instance)
                           .ToArray();

            // get main + preview versions
            void TryGetVersions(out ISemanticVersion mainVersion, out ISemanticVersion previewVersion, Func <(string name, string description, ISemanticVersion version), bool> filter = null)
            {
                mainVersion    = null;
                previewVersion = null;

                // get latest main + preview version
                foreach (var entry in versions)
                {
                    if (filter?.Invoke(entry) == false)
                    {
                        continue;
                    }

                    if (entry.version.IsPrerelease())
                    {
                        previewVersion ??= entry.version;
                    }
                    else
                    {
                        mainVersion ??= entry.version;
                    }

                    if (mainVersion != null)
                    {
                        break; // any other values will be older
                    }
                }

                // normalize values
                if (previewVersion is not null)
                {
                    mainVersion ??= previewVersion; // if every version is prerelease, latest one is the main version
                    if (!previewVersion.IsNewerThan(mainVersion))
                    {
                        previewVersion = null;
                    }
                }
            }

            if (subkey is not null)
            {
                TryGetVersions(out main, out preview, entry => entry.name?.Contains(subkey, StringComparison.OrdinalIgnoreCase) == true || entry.description?.Contains(subkey, StringComparison.OrdinalIgnoreCase) == true);
            }
            if (main is null)
            {
                TryGetVersions(out main, out preview);
            }

            return(main != null);
        }
Exemple #9
0
        /// <summary>Get the mods for which the API should return data.</summary>
        /// <param name="model">The search model.</param>
        /// <param name="apiVersion">The requested API version.</param>
        private IEnumerable <ModSearchEntryModel> GetSearchMods(ModSearchModel model, ISemanticVersion apiVersion)
        {
            if (model == null)
            {
                yield break;
            }

            // yield standard entries
            if (model.Mods != null)
            {
                foreach (ModSearchEntryModel mod in model.Mods)
                {
                    yield return(mod);
                }
            }

            // yield mod update keys if backwards compatible
            if (model.ModKeys != null && model.ModKeys.Any() && !apiVersion.IsNewerThan("2.6-beta.17"))
            {
                foreach (string updateKey in model.ModKeys.Distinct())
                {
                    yield return(new ModSearchEntryModel(updateKey, new[] { updateKey }));
                }
            }
        }
Exemple #10
0
        /// <summary>Validate manifest metadata.</summary>
        /// <param name="mods">The mod manifests to validate.</param>
        /// <param name="apiVersion">The current SMAPI version.</param>
        /// <param name="getUpdateUrl">Get an update URL for an update key (if valid).</param>
        public void ValidateManifests(IEnumerable <IModMetadata> mods, ISemanticVersion apiVersion, Func <string, string> getUpdateUrl)
        {
            mods = mods.ToArray();

            // validate each manifest
            foreach (IModMetadata mod in mods)
            {
                // skip if already failed
                if (mod.Status == ModMetadataStatus.Failed)
                {
                    continue;
                }

                // validate compatibility from internal data
                switch (mod.DataRecord?.Status)
                {
                case ModStatus.Obsolete:
                    mod.SetStatus(ModMetadataStatus.Failed, $"it's obsolete: {mod.DataRecord.StatusReasonPhrase}");
                    continue;

                case ModStatus.AssumeBroken:
                {
                    // get reason
                    string reasonPhrase = mod.DataRecord.StatusReasonPhrase ?? "it's outdated";

                    // get update URLs
                    List <string> updateUrls = new List <string>();
                    foreach (string key in mod.Manifest.UpdateKeys ?? new string[0])
                    {
                        string url = getUpdateUrl(key);
                        if (url != null)
                        {
                            updateUrls.Add(url);
                        }
                    }
                    if (mod.DataRecord.AlternativeUrl != null)
                    {
                        updateUrls.Add(mod.DataRecord.AlternativeUrl);
                    }

                    // default update URL
                    updateUrls.Add("https://smapi.io/compat");

                    // build error
                    string error = $"{reasonPhrase}. Please check for a ";
                    if (mod.DataRecord.StatusUpperVersion == null || mod.Manifest.Version.Equals(mod.DataRecord.StatusUpperVersion))
                    {
                        error += "newer version";
                    }
                    else
                    {
                        error += $"version newer than {mod.DataRecord.StatusUpperVersion}";
                    }
                    error += " at " + string.Join(" or ", updateUrls);

                    mod.SetStatus(ModMetadataStatus.Failed, error);
                }
                    continue;
                }

                // validate SMAPI version
                if (mod.Manifest.MinimumApiVersion?.IsNewerThan(apiVersion) == true)
                {
                    mod.SetStatus(ModMetadataStatus.Failed, $"it needs SMAPI {mod.Manifest.MinimumApiVersion} or later. Please update SMAPI to the latest version to use this mod.");
                    continue;
                }

                // validate DLL / content pack fields
                {
                    bool hasDll        = !string.IsNullOrWhiteSpace(mod.Manifest.EntryDll);
                    bool isContentPack = mod.Manifest.ContentPackFor != null;

                    // validate field presence
                    if (!hasDll && !isContentPack)
                    {
                        mod.SetStatus(ModMetadataStatus.Failed, $"its manifest has no {nameof(IManifest.EntryDll)} or {nameof(IManifest.ContentPackFor)} field; must specify one.");
                        continue;
                    }
                    if (hasDll && isContentPack)
                    {
                        mod.SetStatus(ModMetadataStatus.Failed, $"its manifest sets both {nameof(IManifest.EntryDll)} and {nameof(IManifest.ContentPackFor)}, which are mutually exclusive.");
                        continue;
                    }

                    // validate DLL
                    if (hasDll)
                    {
                        // invalid filename format
                        if (mod.Manifest.EntryDll.Intersect(Path.GetInvalidFileNameChars()).Any())
                        {
                            mod.SetStatus(ModMetadataStatus.Failed, $"its manifest has invalid filename '{mod.Manifest.EntryDll}' for the EntryDLL field.");
                            continue;
                        }

                        // invalid path
                        string assemblyPath = Path.Combine(mod.DirectoryPath, mod.Manifest.EntryDll);
                        if (!File.Exists(assemblyPath))
                        {
                            mod.SetStatus(ModMetadataStatus.Failed, $"its DLL '{mod.Manifest.EntryDll}' doesn't exist.");
                            continue;
                        }
                    }

                    // validate content pack
                    else
                    {
                        // invalid content pack ID
                        if (string.IsNullOrWhiteSpace(mod.Manifest.ContentPackFor.UniqueID))
                        {
                            mod.SetStatus(ModMetadataStatus.Failed, $"its manifest declares {nameof(IManifest.ContentPackFor)} without its required {nameof(IManifestContentPackFor.UniqueID)} field.");
                            continue;
                        }
                    }
                }

                // validate required fields
                {
                    List <string> missingFields = new List <string>(3);

                    if (string.IsNullOrWhiteSpace(mod.Manifest.Name))
                    {
                        missingFields.Add(nameof(IManifest.Name));
                    }
                    if (mod.Manifest.Version == null || mod.Manifest.Version.ToString() == "0.0")
                    {
                        missingFields.Add(nameof(IManifest.Version));
                    }
                    if (string.IsNullOrWhiteSpace(mod.Manifest.UniqueID))
                    {
                        missingFields.Add(nameof(IManifest.UniqueID));
                    }

                    if (missingFields.Any())
                    {
                        mod.SetStatus(ModMetadataStatus.Failed, $"its manifest is missing required fields ({string.Join(", ", missingFields)}).");
                    }
                }
            }

            // validate IDs are unique
            {
                var duplicatesByID = mods
                                     .GroupBy(mod => mod.Manifest?.UniqueID?.Trim(), mod => mod, StringComparer.InvariantCultureIgnoreCase)
                                     .Where(p => p.Count() > 1);
                foreach (var group in duplicatesByID)
                {
                    foreach (IModMetadata mod in group)
                    {
                        if (mod.Status == ModMetadataStatus.Failed)
                        {
                            continue; // don't replace metadata error
                        }
                        mod.SetStatus(ModMetadataStatus.Failed, $"its unique ID '{mod.Manifest.UniqueID}' is used by multiple mods ({string.Join(", ", group.Select(p => p.DisplayName))}).");
                    }
                }
            }
        }
Exemple #11
0
 public SaveManager(ISemanticVersion version, CategorizeChestsModule module)
 {
     _version = version;
     _module  = module;
 }
Exemple #12
0
        /// <summary>Normalise and parse the given condition values.</summary>
        /// <param name="raw">The raw condition values to normalise.</param>
        /// <param name="tokenContext">The tokens available for this content pack.</param>
        /// <param name="formatVersion">The format version specified by the content pack.</param>
        /// <param name="latestFormatVersion">The latest format version.</param>
        /// <param name="minumumTokenVersions">The minimum format versions for newer condition types.</param>
        /// <param name="conditions">The normalised conditions.</param>
        /// <param name="error">An error message indicating why normalisation failed.</param>
        private bool TryParseConditions(InvariantDictionary <string> raw, IContext tokenContext, ISemanticVersion formatVersion, ISemanticVersion latestFormatVersion, InvariantDictionary <ISemanticVersion> minumumTokenVersions, out ConditionDictionary conditions, out string error)
        {
            conditions = new ConditionDictionary();

            // no conditions
            if (raw == null || !raw.Any())
            {
                error = null;
                return(true);
            }

            // parse conditions
            foreach (KeyValuePair <string, string> pair in raw)
            {
                // parse condition key
                if (!TokenName.TryParse(pair.Key, out TokenName name))
                {
                    error      = $"'{pair.Key}' isn't a valid token name";
                    conditions = null;
                    return(false);
                }

                // get token
                IToken token = tokenContext.GetToken(name, enforceContext: false);
                if (token == null)
                {
                    error      = $"'{pair.Key}' isn't a valid condition; must be one of {string.Join(", ", tokenContext.GetTokens(enforceContext: false).Select(p => p.Name).OrderBy(p => p))}";
                    conditions = null;
                    return(false);
                }

                // validate subkeys
                if (!token.CanHaveSubkeys)
                {
                    if (name.HasSubkey())
                    {
                        error      = $"{name.Key} conditions don't allow subkeys (:)";
                        conditions = null;
                        return(false);
                    }
                }
                else if (token.RequiresSubkeys)
                {
                    if (!name.HasSubkey())
                    {
                        error      = $"{name.Key} conditions must specify a token subkey (see readme for usage)";
                        conditions = null;
                        return(false);
                    }
                }

                // check compatibility
                if (minumumTokenVersions.TryGetValue(name.Key, out ISemanticVersion minVersion) && minVersion.IsNewerThan(formatVersion))
                {
                    error      = $"{name} isn't available with format version {formatVersion} (change the {nameof(ContentConfig.Format)} field to {latestFormatVersion} to use newer features)";
                    conditions = null;
                    return(false);
                }

                // parse values
                InvariantHashSet values = this.ParseCommaDelimitedField(pair.Value);
                if (!values.Any())
                {
                    error      = $"{name} can't be empty";
                    conditions = null;
                    return(false);
                }

                // restrict to allowed values
                InvariantHashSet rawValidValues = token.GetAllowedValues(name);
                if (rawValidValues?.Any() == true)
                {
                    InvariantHashSet validValues = new InvariantHashSet(rawValidValues);
                    {
                        string[] invalidValues = values.ExceptIgnoreCase(validValues).ToArray();
                        if (invalidValues.Any())
                        {
                            error      = $"invalid {name} values ({string.Join(", ", invalidValues)}); expected one of {string.Join(", ", validValues)}";
                            conditions = null;
                            return(false);
                        }
                    }
                }

                // perform custom validation
                if (!token.TryCustomValidation(values, out string customError))
                {
                    error      = $"invalid {name} values: {customError}";
                    conditions = null;
                    return(false);
                }

                // create condition
                conditions[name] = new Condition(name, values);
            }

            // return parsed conditions
            error = null;
            return(true);
        }
Exemple #13
0
        /// <summary>Load one patch from a content pack's <c>content.json</c> file.</summary>
        /// <param name="pack">The content pack being loaded.</param>
        /// <param name="contentConfig">The content pack's config.</param>
        /// <param name="entry">The change to load.</param>
        /// <param name="tokenContext">The tokens available for this content pack.</param>
        /// <param name="latestFormatVersion">The latest format version.</param>
        /// <param name="minumumTokenVersions">The minimum format versions for newer condition types.</param>
        /// <param name="logSkip">The callback to invoke with the error reason if loading it fails.</param>
        private bool LoadPatch(ManagedContentPack pack, ContentConfig contentConfig, PatchConfig entry, IContext tokenContext, ISemanticVersion latestFormatVersion, InvariantDictionary <ISemanticVersion> minumumTokenVersions, Action <string> logSkip)
        {
            bool TrackSkip(string reason, bool warn = true)
            {
                this.PatchManager.AddPermanentlyDisabled(new DisabledPatch(entry.LogName, entry.Action, entry.Target, pack, reason));
                if (warn)
                {
                    logSkip(reason);
                }
                return(false);
            }

            try
            {
                // normalise patch fields
                if (entry.When == null)
                {
                    entry.When = new InvariantDictionary <string>();
                }

                // parse action
                if (!Enum.TryParse(entry.Action, true, out PatchType action))
                {
                    return(TrackSkip(string.IsNullOrWhiteSpace(entry.Action)
                        ? $"must set the {nameof(PatchConfig.Action)} field."
                        : $"invalid {nameof(PatchConfig.Action)} value '{entry.Action}', expected one of: {string.Join(", ", Enum.GetNames(typeof(PatchType)))}."
                                     ));
                }

                // parse target asset
                TokenString assetName;
                {
                    if (string.IsNullOrWhiteSpace(entry.Target))
                    {
                        return(TrackSkip($"must set the {nameof(PatchConfig.Target)} field."));
                    }
                    if (!this.TryParseTokenString(entry.Target, tokenContext, out string error, out assetName))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.Target)} is invalid: {error}"));
                    }
                }

                // parse 'enabled'
                bool enabled = true;
                {
                    if (entry.Enabled != null && !this.TryParseEnabled(entry.Enabled, tokenContext, out string error, out enabled))
                    {
                        return(TrackSkip($"invalid {nameof(PatchConfig.Enabled)} value '{entry.Enabled}': {error}"));
                    }
                }

                // parse conditions
                ConditionDictionary conditions;
                {
                    if (!this.TryParseConditions(entry.When, tokenContext, contentConfig.Format, latestFormatVersion, minumumTokenVersions, out conditions, out string error))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.When)} field is invalid: {error}."));
                    }
                }

                // get patch instance
                IPatch patch;
                switch (action)
                {
                // load asset
                case PatchType.Load:
                {
                    // init patch
                    if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, out string error, out TokenString fromAsset))
                    {
                        return(TrackSkip(error));
                    }
                    patch = new LoadPatch(entry.LogName, pack, assetName, conditions, fromAsset, this.Helper.Content.NormaliseAssetName);
                }
                break;

                // edit data
                case PatchType.EditData:
                {
                    // validate
                    if (entry.Entries == null && entry.Fields == null)
                    {
                        return(TrackSkip($"either {nameof(PatchConfig.Entries)} or {nameof(PatchConfig.Fields)} must be specified for a '{action}' change."));
                    }
                    if (entry.Entries != null && entry.Entries.Any(p => p.Value != null && p.Value.Trim() == ""))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.Entries)} can't contain empty values."));
                    }
                    if (entry.Fields != null && entry.Fields.Any(p => p.Value == null || p.Value.Any(n => n.Value == null)))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.Fields)} can't contain empty values."));
                    }

                    // parse entries
                    List <EditDataPatchRecord> entries = new List <EditDataPatchRecord>();
                    if (entry.Entries != null)
                    {
                        foreach (KeyValuePair <string, string> pair in entry.Entries)
                        {
                            if (!this.TryParseTokenString(pair.Key, tokenContext, out string keyError, out TokenString key))
                            {
                                return(TrackSkip($"the {nameof(PatchConfig.Entries)} > '{key}' entry key is invalid: {keyError}."));
                            }
                            if (!this.TryParseTokenString(pair.Value, tokenContext, out string error, out TokenString value))
                            {
                                return(TrackSkip($"the {nameof(PatchConfig.Entries)} > '{key}' entry value is invalid: {error}."));
                            }
                            entries.Add(new EditDataPatchRecord(key, value));
                        }
                    }

                    // parse fields
                    List <EditDataPatchField> fields = new List <EditDataPatchField>();
                    if (entry.Fields != null)
                    {
                        foreach (KeyValuePair <string, IDictionary <int, string> > recordPair in entry.Fields)
                        {
                            if (!this.TryParseTokenString(recordPair.Key, tokenContext, out string keyError, out TokenString key))
                            {
                                return(TrackSkip($"the {nameof(PatchConfig.Fields)} > '{keyError}' field key is invalid: {keyError}."));
                            }
                            foreach (var fieldPair in recordPair.Value)
                            {
                                int field = fieldPair.Key;
                                if (!this.TryParseTokenString(fieldPair.Value, tokenContext, out string valueError, out TokenString value))
                                {
                                    return(TrackSkip($"the {nameof(PatchConfig.Fields)} > '{key}' > {field} field is invalid: {valueError}."));
                                }

                                fields.Add(new EditDataPatchField(key, field, value));
                            }
                        }
                    }

                    // save
                    patch = new EditDataPatch(entry.LogName, pack, assetName, conditions, entries, fields, this.Monitor, this.Helper.Content.NormaliseAssetName);
                }
                break;

                // edit image
                case PatchType.EditImage:
                {
                    // read patch mode
                    PatchMode patchMode = PatchMode.Replace;
                    if (!string.IsNullOrWhiteSpace(entry.PatchMode) && !Enum.TryParse(entry.PatchMode, true, out patchMode))
                    {
                        return(TrackSkip($"the {nameof(PatchConfig.PatchMode)} is invalid. Expected one of these values: [{string.Join(", ", Enum.GetNames(typeof(PatchMode)))}]."));
                    }

                    // save
                    if (!this.TryPrepareLocalAsset(pack, entry.FromFile, tokenContext, out string error, out TokenString fromAsset))
                    {
                        return(TrackSkip(error));
                    }
                    patch = new EditImagePatch(entry.LogName, pack, assetName, conditions, fromAsset, entry.FromArea, entry.ToArea, patchMode, this.Monitor, this.Helper.Content.NormaliseAssetName);
                }
                break;

                default:
                    return(TrackSkip($"unsupported patch type '{action}'."));
                }

                // skip if not enabled
                // note: we process the patch even if it's disabled, so any errors are caught by the modder instead of only failing after the patch is enabled.
                if (!enabled)
                {
                    return(TrackSkip($"{nameof(PatchConfig.Enabled)} is false.", warn: false));
                }

                // save patch
                this.PatchManager.Add(patch);
                return(true);
            }
            catch (Exception ex)
            {
                return(TrackSkip($"error reading info. Technical details:\n{ex}"));
            }
        }
Exemple #14
0
 /// <summary>Construct an instance.</summary>
 /// <param name="id">The unique mod ID.</param>
 /// <param name="installedVersion">The version installed by the local player. This is used for version mapping in some cases.</param>
 /// <param name="updateKeys">The namespaced mod update keys (if available).</param>
 /// <param name="isBroken">Whether the installed version is broken or could not be loaded.</param>
 public ModSearchEntryModel(string id, ISemanticVersion installedVersion, string[] updateKeys, bool isBroken = false)
 {
     this.ID = id;
     this.InstalledVersion = installedVersion;
     this.UpdateKeys       = updateKeys ?? new string[0];
 }
Exemple #15
0
 /// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
 /// <returns>true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.</returns>
 /// <param name="other">An object to compare with this object.</param>
 public bool Equals(ISemanticVersion other)
 {
     return(other != null && this.CompareTo(other) == 0);
 }
Exemple #16
0
 public ModApi(ModConfig config, ISemanticVersion modVersion)
 {
     this.Config     = config;
     this.ModVersion = modVersion;
 }
Exemple #17
0
        /// <summary>Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version.</summary>
        /// <param name="other">The version to compare with this instance.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="other"/> value is null.</exception>
        /// <remarks>The implementation is defined by Semantic Version 2.0 (http://semver.org/).</remarks>
        public int CompareTo(ISemanticVersion other)
        {
            if (other == null)
            {
                throw new ArgumentNullException(nameof(other));
            }

            const int same     = 0;
            const int curNewer = 1;
            const int curOlder = -1;

            // compare stable versions
            if (this.MajorVersion != other.MajorVersion)
            {
                return(this.MajorVersion.CompareTo(other.MajorVersion));
            }
            if (this.MinorVersion != other.MinorVersion)
            {
                return(this.MinorVersion.CompareTo(other.MinorVersion));
            }
            if (this.PatchVersion != other.PatchVersion)
            {
                return(this.PatchVersion.CompareTo(other.PatchVersion));
            }
            if (this.Build == other.Build)
            {
                return(same);
            }

            // stable supercedes pre-release
            bool curIsStable   = string.IsNullOrWhiteSpace(this.Build);
            bool otherIsStable = string.IsNullOrWhiteSpace(other.Build);

            if (curIsStable)
            {
                return(curNewer);
            }
            if (otherIsStable)
            {
                return(curOlder);
            }

            // compare two pre-release tag values
            string[] curParts   = this.Build.Split('.', '-');
            string[] otherParts = other.Build.Split('.', '-');
            for (int i = 0; i < curParts.Length; i++)
            {
                // longer prerelease tag supercedes if otherwise equal
                if (otherParts.Length <= i)
                {
                    return(curNewer);
                }

                // compare if different
                if (curParts[i] != otherParts[i])
                {
                    // compare numerically if possible
                    {
                        if (int.TryParse(curParts[i], out int curNum) && int.TryParse(otherParts[i], out int otherNum))
                        {
                            return(curNum.CompareTo(otherNum));
                        }
                    }

                    // else compare lexically
                    return(string.Compare(curParts[i], otherParts[i], StringComparison.OrdinalIgnoreCase));
                }
            }

            // fallback (this should never happen)
            return(string.Compare(this.ToString(), other.ToString(), StringComparison.InvariantCultureIgnoreCase));
        }
Exemple #18
0
 /// <summary>Construct an instance.</summary>
 /// <param name="mods">The mods to search.</param>
 /// <param name="apiVersion">The SMAPI version installed by the player. If this is null, the API won't provide a recommended update.</param>
 /// <param name="gameVersion">The Secrets Of Grindea version installed by the player.</param>
 /// <param name="platform">The OS on which the player plays.</param>
 /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
 public ModSearchModel(ModSearchEntryModel[] mods, ISemanticVersion apiVersion, ISemanticVersion gameVersion, Platform platform, bool includeExtendedMetadata)
 {
     this.Mods                    = mods.ToArray();
     this.ApiVersion              = apiVersion;
     this.GameVersion             = gameVersion;
     this.Platform                = platform;
     this.IncludeExtendedMetadata = includeExtendedMetadata;
 }
Exemple #19
0
        /// <summary>Read the configuration file for a content pack.</summary>
        /// <param name="contentPack">The content pack.</param>
        /// <param name="rawSchema">The raw config schema from the mod's <c>content.json</c>.</param>
        /// <param name="formatVersion">The content format version.</param>
        public InvariantDictionary <ConfigField> Read(ManagedContentPack contentPack, InvariantDictionary <ConfigSchemaFieldConfig> rawSchema, ISemanticVersion formatVersion)
        {
            InvariantDictionary <ConfigField> config = this.LoadConfigSchema(rawSchema, logWarning: (field, reason) => this.LogWarning(contentPack, $"{nameof(ContentConfig.ConfigSchema)} field '{field}'", reason), formatVersion);

            this.LoadConfigValues(contentPack, config, logWarning: (field, reason) => this.LogWarning(contentPack, $"{this.Filename} > {field}", reason));
            return(config);
        }
 /*********
 ** Protected methods
 *********/
 /// <summary>Construct an instance.</summary>
 /// <param name="version">The version to which this migration applies.</param>
 protected BaseMigration(ISemanticVersion version)
 {
     this.Version = version;
 }
Exemple #21
0
        /*********
        ** 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.InvariantCultureIgnoreCase));

            UpdateKey[]       updateKeys  = this.GetUpdateKeys(search.UpdateKeys, record, wikiEntry).ToArray();
            ModOverrideConfig overrides   = this.Config.Value.ModOverrides.FirstOrDefault(p => p.ID.Equals(search.ID?.Trim(), StringComparison.InvariantCultureIgnoreCase));
            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'.");
                    continue;
                }

                // fetch data
                ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey, allowNonStandardVersions);

                if (data.Error != null)
                {
                    errors.Add(data.Error);
                    continue;
                }

                // handle main version
                if (data.Version != null)
                {
                    ISemanticVersion version = this.GetMappedVersion(data.Version, wikiEntry?.MapRemoteVersions, allowNonStandardVersions);
                    if (version == null)
                    {
                        errors.Add($"The update key '{updateKey}' matches a mod with invalid semantic version '{data.Version}'.");
                        continue;
                    }

                    if (this.IsNewer(version, main?.Version))
                    {
                        main = new ModEntryVersionModel(version, data.Url);
                    }
                }

                // handle optional version
                if (data.PreviewVersion != null)
                {
                    ISemanticVersion version = this.GetMappedVersion(data.PreviewVersion, wikiEntry?.MapRemoteVersions, allowNonStandardVersions);
                    if (version == null)
                    {
                        errors.Add($"The update key '{updateKey}' matches a mod with invalid optional semantic version '{data.PreviewVersion}'.");
                        continue;
                    }

                    if (this.IsNewer(version, optional?.Version))
                    {
                        optional = new ModEntryVersionModel(version, 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")}#{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")}#{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.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()))
                {
                    updates.Add(optional);
                }
                if (this.IsRecommendedUpdate(installedVersion, unofficial?.Version, useBetaChannel: search.IsBroken))
                {
                    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);
        }
Exemple #22
0
        /// <summary>Validate manifest metadata.</summary>
        /// <param name="mods">The mod manifests to validate.</param>
        /// <param name="apiVersion">The current SMAPI version.</param>
        /// <param name="getUpdateUrl">Get an update URL for an update key (if valid).</param>
        public void ValidateManifests(IEnumerable <IModMetadata> mods, ISemanticVersion apiVersion, Func <string, string> getUpdateUrl)
        {
            mods = mods.ToArray();

            // validate each manifest
            foreach (IModMetadata mod in mods)
            {
                // skip if already failed
                if (mod.Status == ModMetadataStatus.Failed)
                {
                    continue;
                }

                // validate compatibility from internal data
                switch (mod.DataRecord?.Status)
                {
                case ModStatus.Obsolete:
                    mod.SetStatus(ModMetadataStatus.Failed, $"it's obsolete: {mod.DataRecord.StatusReasonPhrase}");
                    continue;

                case ModStatus.AssumeBroken:
                {
                    // get reason
                    string reasonPhrase = mod.DataRecord.StatusReasonPhrase ?? "it's no longer compatible";

                    // get update URLs
                    List <string> updateUrls = new List <string>();
                    foreach (string key in mod.Manifest.UpdateKeys ?? new string[0])
                    {
                        string url = getUpdateUrl(key);
                        if (url != null)
                        {
                            updateUrls.Add(url);
                        }
                    }
                    if (mod.DataRecord.AlternativeUrl != null)
                    {
                        updateUrls.Add(mod.DataRecord.AlternativeUrl);
                    }

                    // default update URL
                    updateUrls.Add("https://smapi.io/mods");

                    // build error
                    string error = $"{reasonPhrase}. Please check for a ";
                    if (mod.DataRecord.StatusUpperVersion == null || mod.Manifest.Version.Equals(mod.DataRecord.StatusUpperVersion))
                    {
                        error += "newer version";
                    }
                    else
                    {
                        error += $"version newer than {mod.DataRecord.StatusUpperVersion}";
                    }
                    error += " at " + string.Join(" or ", updateUrls);

                    mod.SetStatus(ModMetadataStatus.Failed, error);
                }
                    continue;
                }

                // validate SMAPI version
                if (mod.Manifest.MinimumApiVersion?.IsNewerThan(apiVersion) == true)
                {
                    mod.SetStatus(ModMetadataStatus.Failed, $"it needs SMAPI {mod.Manifest.MinimumApiVersion} or later. Please update SMAPI to the latest version to use this mod.");
                    continue;
                }

                // validate DLL / content pack fields
                {
                    bool hasDll        = !string.IsNullOrWhiteSpace(mod.Manifest.EntryDll);
                    bool isContentPack = mod.Manifest.ContentPackFor != null;

                    // validate field presence
                    if (!hasDll && !isContentPack)
                    {
                        mod.SetStatus(ModMetadataStatus.Failed, $"its manifest has no {nameof(IManifest.EntryDll)} or {nameof(IManifest.ContentPackFor)} field; must specify one.");
                        continue;
                    }
                    if (hasDll && isContentPack)
                    {
                        mod.SetStatus(ModMetadataStatus.Failed, $"its manifest sets both {nameof(IManifest.EntryDll)} and {nameof(IManifest.ContentPackFor)}, which are mutually exclusive.");
                        continue;
                    }

                    // validate DLL
                    if (hasDll)
                    {
                        // invalid filename format
                        if (mod.Manifest.EntryDll.Intersect(Path.GetInvalidFileNameChars()).Any())
                        {
                            mod.SetStatus(ModMetadataStatus.Failed, $"its manifest has invalid filename '{mod.Manifest.EntryDll}' for the EntryDLL field.");
                            continue;
                        }

                        // invalid path
                        if (!File.Exists(Path.Combine(mod.DirectoryPath, mod.Manifest.EntryDll)))
                        {
                            mod.SetStatus(ModMetadataStatus.Failed, $"its DLL '{mod.Manifest.EntryDll}' doesn't exist.");
                            continue;
                        }

                        // invalid capitalization
                        string actualFilename = new DirectoryInfo(mod.DirectoryPath).GetFiles(mod.Manifest.EntryDll).FirstOrDefault()?.Name;
                        if (actualFilename != mod.Manifest.EntryDll)
                        {
                            mod.SetStatus(ModMetadataStatus.Failed, $"its {nameof(IManifest.EntryDll)} value '{mod.Manifest.EntryDll}' doesn't match the actual file capitalization '{actualFilename}'. The capitalization must match for crossplatform compatibility.");
                            continue;
                        }
                    }

                    // validate content pack
                    else
                    {
                        // invalid content pack ID
                        if (string.IsNullOrWhiteSpace(mod.Manifest.ContentPackFor.UniqueID))
                        {
                            mod.SetStatus(ModMetadataStatus.Failed, $"its manifest declares {nameof(IManifest.ContentPackFor)} without its required {nameof(IManifestContentPackFor.UniqueID)} field.");
                            continue;
                        }
                    }
                }

                // validate required fields
                {
                    List <string> missingFields = new List <string>(3);

                    if (string.IsNullOrWhiteSpace(mod.Manifest.Name))
                    {
                        missingFields.Add(nameof(IManifest.Name));
                    }
                    if (mod.Manifest.Version == null || mod.Manifest.Version.ToString() == "0.0")
                    {
                        missingFields.Add(nameof(IManifest.Version));
                    }
                    if (string.IsNullOrWhiteSpace(mod.Manifest.UniqueID))
                    {
                        missingFields.Add(nameof(IManifest.UniqueID));
                    }

                    if (missingFields.Any())
                    {
                        mod.SetStatus(ModMetadataStatus.Failed, $"its manifest is missing required fields ({string.Join(", ", missingFields)}).");
                        continue;
                    }
                }

                // validate ID format
                if (!PathUtilities.IsSlug(mod.Manifest.UniqueID))
                {
                    mod.SetStatus(ModMetadataStatus.Failed, "its manifest specifies an invalid ID (IDs must only contain letters, numbers, underscores, periods, or hyphens).");
                }
            }

            // validate IDs are unique
            {
                var duplicatesByID = mods
                                     .GroupBy(mod => mod.Manifest?.UniqueID?.Trim(), mod => mod, StringComparer.OrdinalIgnoreCase)
                                     .Where(p => p.Count() > 1);
                foreach (var group in duplicatesByID)
                {
                    foreach (IModMetadata mod in group)
                    {
                        if (mod.Status == ModMetadataStatus.Failed)
                        {
                            continue; // don't replace metadata error
                        }
                        string folderList = string.Join(", ",
                                                        from entry in @group
                                                        let relativePath = entry.GetRelativePathWithRoot()
                                                                           orderby relativePath
                                                                           select $"{relativePath} ({entry.Manifest.Version})"
                                                        );
                        mod.SetStatus(ModMetadataStatus.Failed, $"you have multiple copies of this mod installed. Found in folders: {folderList}.");
                    }
                }
            }
        }
Exemple #23
0
        /// <summary>Create a temporary content pack to read files from a directory. Temporary content packs will not appear in the SMAPI log and update checks will not be performed.</summary>
        /// <param name="directoryPath">The absolute directory path containing the content pack files.</param>
        /// <param name="id">The content pack's unique ID.</param>
        /// <param name="name">The content pack name.</param>
        /// <param name="description">The content pack description.</param>
        /// <param name="author">The content pack author's name.</param>
        /// <param name="version">The content pack version.</param>
        public IContentPack CreateTemporary(string directoryPath, string id, string name, string description, string author, ISemanticVersion version)
        {
            // validate
            if (string.IsNullOrWhiteSpace(directoryPath))
            {
                throw new ArgumentNullException(nameof(directoryPath));
            }
            if (string.IsNullOrWhiteSpace(id))
            {
                throw new ArgumentNullException(nameof(id));
            }
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentNullException(nameof(name));
            }
            if (!Directory.Exists(directoryPath))
            {
                throw new ArgumentException($"Can't create content pack for directory path '{directoryPath}' because no such directory exists.");
            }

            // create manifest
            IManifest manifest = new Manifest(
                uniqueID: id,
                name: name,
                author: author,
                description: description,
                version: version,
                contentPackFor: this.ModID
                );

            // create content pack
            return(this.CreateContentPack(directoryPath, manifest));
        }
Exemple #24
0
        public IContentPack CreateTransitionalContentPack(string directoryPath, string id, string name, string description, string author, ISemanticVersion version)
        {
            // raise deprecation notice
            this.DeprecationManager.Warn($"{nameof(IModHelper)}.{nameof(IModHelper.CreateTransitionalContentPack)}", "2.5", DeprecationLevel.Notice);

            // validate
            if (string.IsNullOrWhiteSpace(directoryPath))
            {
                throw new ArgumentNullException(nameof(directoryPath));
            }
            if (string.IsNullOrWhiteSpace(id))
            {
                throw new ArgumentNullException(nameof(id));
            }
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentNullException(nameof(name));
            }
            if (!Directory.Exists(directoryPath))
            {
                throw new ArgumentException($"Can't create content pack for directory path '{directoryPath}' because no such directory exists.");
            }

            // create manifest
            IManifest manifest = new Manifest
            {
                Name           = name,
                Author         = author,
                Description    = description,
                Version        = version,
                UniqueID       = id,
                UpdateKeys     = new string[0],
                ContentPackFor = new ManifestContentPackFor {
                    UniqueID = this.ModID
                }
            };

            // create content pack
            return(this.CreateContentPack(directoryPath, manifest));
        }
Exemple #25
0
 /// <summary>Get metadata about a set of mods from the web API.</summary>
 /// <param name="mods">The mod keys for which to fetch the latest version.</param>
 /// <param name="apiVersion">The SMAPI version installed by the player. If this is null, the API won't provide a recommended update.</param>
 /// <param name="gameVersion">The Stardew Valley version installed by the player.</param>
 /// <param name="platform">The OS on which the player plays.</param>
 /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
 public IDictionary <string, ModEntryModel> GetModInfo(ModSearchEntryModel[] mods, ISemanticVersion apiVersion, ISemanticVersion gameVersion, Platform platform, bool includeExtendedMetadata = false)
 {
     return(this.Post <ModSearchModel, ModEntryModel[]>(
                $"v{this.Version}/mods",
                new ModSearchModel(mods, apiVersion, gameVersion, platform, includeExtendedMetadata)
                ).ToDictionary(p => p.ID));
 }
Exemple #26
0
 /// <summary>Get whether this version is newer than the specified version.</summary>
 /// <param name="other">The version to compare with this instance.</param>
 public bool IsNewerThan(ISemanticVersion other)
 {
     return(this.CompareTo(other) > 0);
 }
Exemple #27
0
 /// <summary>Get whether a <paramref name="current"/> version is newer than an <paramref name="other"/> version.</summary>
 /// <param name="current">The current version.</param>
 /// <param name="other">The other version.</param>
 private bool IsNewer(ISemanticVersion current, ISemanticVersion other)
 {
     return(current != null && (other == null || other.IsOlderThan(current)));
 }
Exemple #28
0
 /// <summary>Get whether this version is between two specified versions (inclusively).</summary>
 /// <param name="min">The minimum version.</param>
 /// <param name="max">The maximum version.</param>
 public bool IsBetween(ISemanticVersion min, ISemanticVersion max)
 {
     return(this.CompareTo(min) >= 0 && this.CompareTo(max) <= 0);
 }
Exemple #29
0
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 /// <param name="key">The field key.</param>
 /// <param name="value">The field value.</param>
 /// <param name="isDefault">Whether this field should only be applied if it's not already set.</param>
 /// <param name="lowerVersion">The lowest version in the range, or <c>null</c> for all past versions.</param>
 /// <param name="upperVersion">The highest version in the range, or <c>null</c> for all future versions.</param>
 public ModDataField(ModDataFieldKey key, string value, bool isDefault, ISemanticVersion lowerVersion, ISemanticVersion upperVersion)
 {
     this.Key          = key;
     this.Value        = value;
     this.IsDefault    = isDefault;
     this.LowerVersion = lowerVersion;
     this.UpperVersion = upperVersion;
 }
Exemple #30
0
 /// <summary>Get whether this version is older than the specified version.</summary>
 /// <param name="other">The version to compare with this instance.</param>
 public bool IsOlderThan(ISemanticVersion other)
 {
     return(this.Version.IsOlderThan(other));
 }