示例#1
0
 ////Taken from https://github.com/Pathoschild/SMAPI/blob/924c3a5d3fe6bfad483834112883156bdf202b57/src/SMAPI/Framework/SCore.cs#L669
 /// <summary>Get whether a given version should be offered to the user as an update.</summary>
 /// <param name="currentVersion">The current semantic version.</param>
 /// <param name="newVersion">The target semantic version.</param>
 /// <param name="useBetaChannel">Whether the user enabled the beta channel and should be offered pre-release updates.</param>
 private bool IsValidUpdate(ISemanticVersion currentVersion, ISemanticVersion newVersion, bool useBetaChannel)
 {
     return
         (newVersion != null &&
          newVersion.IsNewerThan(currentVersion) &&
          (useBetaChannel || !newVersion.IsPrerelease()));
 }
示例#2
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);
        }
示例#3
0
        public bool GetUpdateStatuses(out IList <ModStatus> statuses)
        {
            statuses = new List <ModStatus>();


            object registry = this.helper.ModRegistry.GetType()
                              .GetField("Registry", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(this.helper.ModRegistry);

            bool addedNonSkippedStatus = false;

            foreach (object modMetaData in (IEnumerable <object>)registry.GetType().GetField("Mods", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(registry))
            {
                ModEntryModel result   = GetInstanceProperty <ModEntryModel>(modMetaData, "UpdateCheckData");
                IManifest     manifest = GetInstanceProperty <IManifest>(modMetaData, "Manifest");

                if (result == null)
                {
                    statuses.Add(new ModStatus(UpdateStatus.Skipped, manifest, "", null, "SMAPI didn't check for an update"));
                    continue;
                }

                if (!(bool)modMetaData.GetType().GetMethod("HasValidUpdateKeys").Invoke(modMetaData, null))
                {
                    statuses.Add(new ModStatus(UpdateStatus.Skipped, manifest, "", null, "Mod has no update keys"));
                    continue;
                }

                ModDataRecordVersionedFields dataRecord =
                    GetInstanceProperty <ModDataRecordVersionedFields>(modMetaData, "DataRecord");

                //This section largely taken from https://github.com/Pathoschild/SMAPI/blob/924c3a5d3fe6bfad483834112883156bdf202b57/src/SMAPI/Framework/SCore.cs#L618-L630
                bool             useBetaInfo       = result.HasBetaInfo && Constants.ApiVersion.IsPrerelease();
                ISemanticVersion localVersion      = dataRecord?.GetLocalVersionForUpdateChecks(manifest.Version) ?? manifest.Version;
                ISemanticVersion latestVersion     = dataRecord?.GetRemoteVersionForUpdateChecks(result.Main?.Version) ?? result.Main?.Version;
                ISemanticVersion optionalVersion   = dataRecord?.GetRemoteVersionForUpdateChecks(result.Optional?.Version) ?? result.Optional?.Version;
                ISemanticVersion unofficialVersion = useBetaInfo ? result.UnofficialForBeta?.Version : result.Unofficial?.Version;

                if (this.IsValidUpdate(localVersion, latestVersion, useBetaChannel: true))
                {
                    statuses.Add(new ModStatus(UpdateStatus.OutOfDate, manifest, result.Main?.Url, latestVersion.ToString()));
                }
                else if (this.IsValidUpdate(localVersion, optionalVersion, useBetaChannel: localVersion.IsPrerelease()))
                {
                    statuses.Add(new ModStatus(UpdateStatus.OutOfDate, manifest, result.Optional?.Url, optionalVersion.ToString()));
                }
                else if (this.IsValidUpdate(localVersion, unofficialVersion, useBetaChannel: GetEnumName(modMetaData, "Status") == "Failed"))
                {
                    statuses.Add(new ModStatus(UpdateStatus.OutOfDate, manifest, useBetaInfo ? result.UnofficialForBeta?.Url : result.Unofficial?.Url, unofficialVersion.ToString()));
                }
                else
                {
                    string       updateURL    = null;
                    UpdateStatus updateStatus = UpdateStatus.UpToDate;
                    if (localVersion.Equals(latestVersion))
                    {
                        updateURL = result.Main?.Url;
                    }
                    else if (localVersion.Equals(optionalVersion))
                    {
                        updateURL = result.Optional?.Url;
                    }
                    else if (localVersion.Equals(unofficialVersion))
                    {
                        updateURL = useBetaInfo ? result.UnofficialForBeta?.Url : result.Unofficial?.Url;
                    }
                    else if (latestVersion != null && this.IsValidUpdate(latestVersion, localVersion, useBetaChannel: true))
                    {
                        updateURL    = result.Main?.Url;
                        updateStatus = UpdateStatus.VeryNew;
                    }
                    else if (optionalVersion != null && this.IsValidUpdate(optionalVersion, localVersion, useBetaChannel: localVersion.IsPrerelease()))
                    {
                        updateURL    = result.Optional?.Url;
                        updateStatus = UpdateStatus.VeryNew;
                    }
                    else if (unofficialVersion != null && this.IsValidUpdate(unofficialVersion, localVersion, useBetaChannel: GetEnumName(modMetaData, "Status") == "Failed"))
                    {
                        updateURL    = useBetaInfo ? result.UnofficialForBeta?.Url : result.Unofficial?.Url;
                        updateStatus = UpdateStatus.VeryNew;
                    }

                    if (updateURL != null)
                    {
                        statuses.Add(new ModStatus(updateStatus, manifest, updateURL));
                    }
                    else if (result.Errors != null && result.Errors.Any())
                    {
                        statuses.Add(new ModStatus(UpdateStatus.Error, manifest, "", "", result.Errors[0]));
                    }
                    else
                    {
                        statuses.Add(new ModStatus(UpdateStatus.Error, manifest, "", "", "Unknown Error"));
                    }
                }

                addedNonSkippedStatus = true;
            }

            return(addedNonSkippedStatus);
        }
示例#4
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.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);
        }
        public bool GetUpdateStatuses(out IList <ModStatus> statuses)
        {
            statuses = new List <ModStatus>();


            object registry = this.helper.ModRegistry.GetType()
                              .GetField("Registry", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(this.helper.ModRegistry);

            foreach (object modMetaData in (IEnumerable <object>)registry.GetType()
                     .GetMethod("GetAll", BindingFlags.Public | BindingFlags.Instance)
                     .Invoke(registry, new object[] { true, true }))
            {
                ModEntryModel updateCheckModel = GetInstanceProperty <ModEntryModel>(modMetaData, "UpdateCheckData");

                if (updateCheckModel == null)
                {
                    return(false);
                }

                IManifest modManifest = GetInstanceProperty <IManifest>(modMetaData, "Manifest");

                ModEntryVersionModel latestModEntryVersionModel     = updateCheckModel.Main;
                ModEntryVersionModel optionalModEntryVersionModel   = updateCheckModel.Optional;
                ModEntryVersionModel unofficialModEntryVersionModel = updateCheckModel.Unofficial;

                // get versions
                ISemanticVersion localVersion      = modManifest.Version;
                ISemanticVersion latestVersion     = latestModEntryVersionModel?.Version;
                ISemanticVersion optionalVersion   = optionalModEntryVersionModel?.Version;
                ISemanticVersion unofficialVersion = unofficialModEntryVersionModel?.Version;

                UpdateStatus         status     = UpdateStatus.OutOfDate;
                ModEntryVersionModel whichModel = null;
                ISemanticVersion     updateVersion;
                string error = null;

                // get update alerts
                if (IsValidUpdate(localVersion, latestVersion, useBetaChannel: true))
                {
                    whichModel    = latestModEntryVersionModel;
                    updateVersion = latestVersion;
                }
                else if (IsValidUpdate(localVersion, optionalVersion, useBetaChannel: localVersion.IsPrerelease()))
                {
                    whichModel    = optionalModEntryVersionModel;
                    updateVersion = optionalVersion;
                }
                else if (IsValidUpdate(localVersion, unofficialVersion, useBetaChannel: true))
                //Different from SMAPI: useBetaChannel is always true
                {
                    whichModel = unofficialModEntryVersionModel;
                    unofficialModEntryVersionModel.Url = $"https://stardewvalleywiki.com/Modding:SMAPI_compatibility#{GenerateAnchor(modManifest.Name)}";
                    updateVersion = unofficialVersion;
                }
                else
                {
                    if (updateCheckModel.Errors.Length > 0)
                    {
                        status        = UpdateStatus.Error;
                        error         = updateCheckModel.Errors[0];
                        updateVersion = modManifest.Version;
                    }
                    else
                    {
                        updateVersion = modManifest.Version;
                        status        = UpdateStatus.UpToDate;

                        if (latestVersion != null && (latestVersion.Equals(localVersion) || IsValidUpdate(latestVersion, localVersion, true)))
                        {
                            whichModel = latestModEntryVersionModel;
                        }
                        else if (optionalVersion != null && (optionalVersion.Equals(localVersion) || IsValidUpdate(optionalVersion, localVersion, true)))
                        {
                            whichModel = optionalModEntryVersionModel;
                        }
                        else if (unofficialVersion != null && (unofficialVersion.Equals(localVersion) || IsValidUpdate(unofficialVersion, localVersion, true)))
                        {
                            whichModel = unofficialModEntryVersionModel;
                        }
                        else
                        {
                            status = UpdateStatus.Skipped;
                        }
                    }
                }

                statuses.Add(new ModStatus(status, modManifest.UniqueID, modManifest.Name, modManifest.Author,
                                           whichModel?.Url ?? "",
                                           modManifest.Version.ToString(), updateVersion?.ToString() ?? "", error));
            }

            return(true);
        }