示例#1
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);
        }
示例#2
0
        /// <summary>Asynchronously check for a new version of SMAPI and any installed mods, and print alerts to the console if an update is available.</summary>
        /// <param name="mods">The mods to include in the update check (if eligible).</param>
        private void CheckForUpdatesAsync(IModMetadata[] mods)
        {
            if (!this.Settings.CheckForUpdates)
            {
                return;
            }

            new Thread(() =>
            {
                // create client
                WebApiClient client = new WebApiClient(this.Settings.WebApiBaseUrl, Constants.ApiVersion);

                // check SMAPI version
                try
                {
                    this.Monitor.Log("Checking for SMAPI update...", LogLevel.Trace);

                    ModInfoModel response = client.GetModInfo($"GitHub:{this.Settings.GitHubProjectName}").Single().Value;
                    if (response.Error != null)
                    {
                        this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.", LogLevel.Warn);
                        this.Monitor.Log($"Error: {response.Error}");
                    }
                    else if (new SemanticVersion(response.Version).IsNewerThan(Constants.ApiVersion))
                    {
                        this.Monitor.Log($"You can update SMAPI to {response.Version}: {response.Url}", LogLevel.Alert);
                    }
                    else
                    {
                        this.VerboseLog("   OK.");
                    }
                }
                catch (Exception ex)
                {
                    this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.", LogLevel.Warn);
                    this.Monitor.Log($"Error: {ex.GetLogSummary()}");
                }

                // check mod versions
                try
                {
                    // log issues
                    if (this.Settings.VerboseLogging)
                    {
                        this.VerboseLog("Validating mod update keys...");
                        foreach (IModMetadata mod in mods)
                        {
                            if (mod.Manifest == null)
                            {
                                this.VerboseLog($"   {mod.DisplayName}: no manifest.");
                            }
                            else if (mod.Manifest.UpdateKeys == null || !mod.Manifest.UpdateKeys.Any())
                            {
                                this.VerboseLog($"   {mod.DisplayName}: no update keys.");
                            }
                        }
                    }

                    // prepare update keys
                    Dictionary <string, IModMetadata[]> modsByKey =
                        (
                            from mod in mods
                            where mod.Manifest?.UpdateKeys != null
                            from key in mod.Manifest.UpdateKeys
                            select new { key, mod }
                        )
                        .GroupBy(p => p.key, StringComparer.InvariantCultureIgnoreCase)
                        .ToDictionary(
                            group => group.Key,
                            group => group.Select(p => p.mod).ToArray(),
                            StringComparer.InvariantCultureIgnoreCase
                            );

                    // fetch results
                    this.Monitor.Log($"Checking for updates to {modsByKey.Keys.Count} keys...", LogLevel.Trace);
                    var results =
                        (
                            from entry in client.GetModInfo(modsByKey.Keys.ToArray())
                            from mod in modsByKey[entry.Key]
                            orderby mod.DisplayName
                            select new { entry.Key, Mod = mod, Info = entry.Value }
                        )
                        .ToArray();

                    // extract latest versions
                    IDictionary <IModMetadata, ModInfoModel> updatesByMod = new Dictionary <IModMetadata, ModInfoModel>();
                    foreach (var result in results)
                    {
                        IModMetadata mod  = result.Mod;
                        ModInfoModel info = result.Info;

                        // handle error
                        if (info.Error != null)
                        {
                            this.Monitor.Log($"   {mod.DisplayName} ({result.Key}): update error: {info.Error}", LogLevel.Trace);
                            continue;
                        }

                        // track update
                        ISemanticVersion localVersion = mod.DataRecord != null
                            ? new SemanticVersion(mod.DataRecord.GetLocalVersionForUpdateChecks(mod.Manifest.Version.ToString()))
                            : mod.Manifest.Version;
                        ISemanticVersion latestVersion = new SemanticVersion(mod.DataRecord != null
                            ? mod.DataRecord.GetRemoteVersionForUpdateChecks(new SemanticVersion(info.Version).ToString())
                            : info.Version
                                                                             );
                        bool isUpdate = latestVersion.IsNewerThan(localVersion);
                        this.VerboseLog($"   {mod.DisplayName} ({result.Key}): {(isUpdate ? $"{mod.Manifest.Version}{(!localVersion.Equals(mod.Manifest.Version) ? $" [{localVersion}]" : "")} => {info.Version}{(!latestVersion.Equals(new SemanticVersion(info.Version)) ? $" [{latestVersion}]" : "")}" : "OK")}.");
                        if (isUpdate)
                        {
                            if (!updatesByMod.TryGetValue(mod, out ModInfoModel other) || latestVersion.IsNewerThan(other.Version))
                            {
                                updatesByMod[mod] = info;
                            }
                        }
                    }

                    // output
                    if (updatesByMod.Any())
                    {
                        this.Monitor.Newline();
                        this.Monitor.Log($"You can update {updatesByMod.Count} mod{(updatesByMod.Count != 1 ? "s" : "")}:", LogLevel.Alert);
                        foreach (var entry in updatesByMod.OrderBy(p => p.Key.DisplayName))
                        {
                            this.Monitor.Log($"   {entry.Key.DisplayName} {entry.Value.Version}: {entry.Value.Url}", LogLevel.Alert);
                        }
                    }
                }
                catch (Exception ex)
                {
                    this.Monitor.Log($"Couldn't check for new mod versions:\n{ex.GetLogSummary()}", LogLevel.Trace);
                }
            }).Start();
        }
        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);
        }