Exemplo n.º 1
0
        /// <summary>Get metadata about a mod.</summary>
        /// <param name="id">The CurseForge mod ID.</param>
        /// <returns>Returns the mod info if found, else <c>null</c>.</returns>
        public async Task <CurseForgeMod> GetModAsync(long id)
        {
            // get raw data
            ModModel mod = await this.Client
                           .GetAsync($"addon/{id}")
                           .As <ModModel>();

            if (mod == null)
            {
                return(null);
            }

            // get latest versions
            string           invalidVersion = null;
            ISemanticVersion latest         = null;

            foreach (ModFileModel file in mod.LatestFiles)
            {
                // extract version
                ISemanticVersion version;
                {
                    string raw = this.GetRawVersion(file);
                    if (raw == null)
                    {
                        continue;
                    }

                    if (!SemanticVersion.TryParse(raw, out version))
                    {
                        invalidVersion ??= raw;
                        continue;
                    }
                }

                // track latest version
                if (latest == null || version.IsNewerThan(latest))
                {
                    latest = version;
                }
            }

            // get error
            string error = null;

            if (latest == null && invalidVersion == null)
            {
                error = mod.LatestFiles.Any()
                    ? $"CurseForge mod {id} has no downloads which specify the version in a recognised format."
                    : $"CurseForge mod {id} has no downloads.";
            }

            // generate result
            return(new CurseForgeMod
            {
                Name = mod.Name,
                LatestVersion = latest?.ToString() ?? invalidVersion,
                Url = mod.WebsiteUrl,
                Error = error
            });
        }
Exemplo n.º 2
0
 /// <summary>
 /// Build save data for the current game state.
 /// </summary>
 private SaveData GetSerializableData()
 {
     return(new SaveData
     {
         Version = Version.ToString(),
         ChestEntries = BuildChestEntries()
     });
 }
Exemplo n.º 3
0
        /*********
        ** Internal methods
        *********/
        /// <summary>Get the SMAPI version to recommend for an older game version, if any.</summary>
        /// <param name="version">The game version to search.</param>
        /// <returns>Returns the compatible SMAPI version, or <c>null</c> if none was found.</returns>
        internal static ISemanticVersion GetCompatibleApiVersion(ISemanticVersion version)
        {
            // This covers all officially supported public game updates. It might seem like version
            // ranges would be better, but the given SMAPI versions may not be compatible with
            // intermediate unlisted versions (e.g. private beta updates).
            //
            // Nonstandard versions are normalized by GameVersion (e.g. 1.07 => 1.0.7).
            switch (version.ToString())
            {
            case "1.4.1":
            case "1.4.0":
                return(new SemanticVersion("3.0.1"));

            case "1.3.36":
                return(new SemanticVersion("2.11.2"));

            case "1.3.33":
            case "1.3.32":
                return(new SemanticVersion("2.10.2"));

            case "1.3.28":
                return(new SemanticVersion("2.7.0"));

            case "1.2.33":
            case "1.2.32":
            case "1.2.31":
            case "1.2.30":
                return(new SemanticVersion("2.5.5"));

            case "1.2.29":
            case "1.2.28":
            case "1.2.27":
            case "1.2.26":
                return(new SemanticVersion("1.13.1"));

            case "1.1.1":
            case "1.1.0":
                return(new SemanticVersion("1.9.0"));

            case "1.0.7.1":
            case "1.0.7":
            case "1.0.6":
            case "1.0.5.2":
            case "1.0.5.1":
            case "1.0.5":
            case "1.0.4":
            case "1.0.3":
            case "1.0.2":
            case "1.0.1":
            case "1.0.0":
                return(new SemanticVersion("0.40.0"));

            default:
                return(null);
            }
        }
Exemplo n.º 4
0
        /// <summary>Get a semantic remote version for update checks.</summary>
        /// <param name="version">The remote version to normalise.</param>
        public ISemanticVersion GetRemoteVersionForUpdateChecks(ISemanticVersion version)
        {
            if (version == null)
            {
                return(null);
            }

            string rawVersion = this.DataRecord.GetRemoteVersionForUpdateChecks(version.ToString());

            return(rawVersion != null
                ? new SemanticVersion(rawVersion)
                : version);
        }
Exemplo n.º 5
0
        /*********
        ** Internal methods
        *********/
        /// <summary>Get the SMAPI version to recommend for an older game version, if any.</summary>
        /// <param name="version">The game version to search.</param>
        /// <returns>Returns the compatible SMAPI version, or <c>null</c> if none was found.</returns>
        internal static ISemanticVersion GetCompatibleApiVersion(ISemanticVersion version)
        {
            switch (version.ToString())
            {
                case "1.3.28":
                    return new SemanticVersion(2, 7, 0);

                case "1.2.30":
                case "1.2.31":
                case "1.2.32":
                case "1.2.33":
                    return new SemanticVersion(2, 5, 5);
            }

            return null;
        }
Exemplo n.º 6
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);
        }
Exemplo n.º 7
0
        private void Multiplayer_ModMessageReceived(object sender, StardewModdingAPI.Events.ModMessageReceivedEventArgs e)
        {
            if (!string.Equals(e.FromModID, CurrentModId, StringComparison.OrdinalIgnoreCase))
            {
                return;
            }

            ISemanticVersion ver        = Helper.Multiplayer.GetConnectedPlayer(e.FromPlayerID)?.GetMod(CurrentModId)?.Version ?? null;
            bool             compatible = ver?.IsNewerThan(CurrentVersion) ?? false;

            if (!compatible)
            {
                Monitor.Log(string.Format("Cannot use this mod during this online session because the host is currently using a more recent version of the mod. Update this mod to enable it on this server. RemoteVer: {0}. LocalVer: {1}", ver?.ToString() ?? "Null", CurrentVersion));
                Settings.Remote = Settings.DefaultDisabled;
                return;
            }

            foreach (Farmer farmer in Game1.getAllFarmers())
            {
                if (farmer.UniqueMultiplayerID == e.FromPlayerID)
                {
                    if (farmer.IsMainPlayer)
                    {
                        // Message received from server
                        if (string.Equals(e.Type, MultiplayerSettingsMessageType, StringComparison.OrdinalIgnoreCase))
                        {
                            try
                            {
                                Settings data = e.ReadAs <Settings>();

                                if (data != null)
                                {
                                    Settings.Remote = data;
                                    Monitor.Log(string.Format("Remote settings updated from server. FarmerId: {0}.", e.FromPlayerID));
                                }
                            }
                            catch (Exception ex)
                            {
                                Monitor.Log(string.Format("Remote settings cannot be updated from server. FarmerId: {0}. Ex: {1}. Msg: {2}. InnerEx: {3}", e.FromPlayerID, ex.GetType().Name, ex.Message, ex.InnerException?.GetType().Name ?? "None"));
                            }
                        }
                        else
                        {
                            Monitor.Log(string.Format("Unknown mod message received from server. FarmerId: {0}. Type: {1}.", e.FromPlayerID, e.Type));
                        }
                    }
                    else
                    {
                        Monitor.Log(string.Format("Remote mod message received from a non-host farmer. FarmerId: {0}. Type: {1}.", e.FromPlayerID, e.Type));
                    }

                    return;
                }
            }

            Monitor.Log(string.Format("Remote mod message received from an unknown farmer. PlayerId: {0}. Type: {1}", e.FromPlayerID, e.Type));
        }
Exemplo n.º 8
0
        /*********
        ** Public methods
        *********/
        /// <summary>Get whether the specified version is compatible according to this metadata.</summary>
        /// <param name="version">The current version of the matching mod.</param>
        public bool IsCompatible(ISemanticVersion version)
        {
            ISemanticVersion lowerVersion = this.LowerVersion != null ? new SemanticVersion(this.LowerVersion) : null;
            ISemanticVersion upperVersion = new SemanticVersion(this.UpperVersion);

            // ignore versions not in range
            if (lowerVersion != null && version.IsOlderThan(lowerVersion))
            {
                return(true);
            }
            if (version.IsNewerThan(upperVersion))
            {
                return(true);
            }

            // allow versions matching override
            return(!string.IsNullOrWhiteSpace(this.ForceCompatibleVersion) && Regex.IsMatch(version.ToString(), this.ForceCompatibleVersion, RegexOptions.IgnoreCase));
        }
Exemplo n.º 9
0
        /*********
        ** Public methods
        *********/
        /// <summary>Get whether the specified version is compatible according to this metadata.</summary>
        /// <param name="version">The current version of the matching mod.</param>
        public bool IsCompatible(ISemanticVersion version)
        {
            ISemanticVersion incompatibleVersion = new SemanticVersion(this.Version);

            // allow newer versions
            if (version.IsNewerThan(incompatibleVersion))
            {
                return(true);
            }

            // allow versions matching override
            return(!string.IsNullOrWhiteSpace(this.ForceCompatibleVersion) && Regex.IsMatch(version.ToString(), this.ForceCompatibleVersion, RegexOptions.IgnoreCase));
        }
Exemplo n.º 10
0
 /// <summary>Get a semantic local version for update checks.</summary>
 /// <param name="version">The remote version to normalize.</param>
 public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version)
 {
     return(this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version.ToString(), out string newVersion)
         ? new SemanticVersion(newVersion)
         : version);
 }
Exemplo n.º 11
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
                    {
                        int curNum, otherNum;
                        if (int.TryParse(curParts[i], out curNum) && int.TryParse(otherParts[i], out 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));
        }
Exemplo n.º 12
0
 /// <summary>Get whether the cache entry is up-to-date for the given assembly hash.</summary>
 /// <param name="paths">The paths for the cached assembly.</param>
 /// <param name="hash">The MD5 hash of the original assembly.</param>
 /// <param name="currentVersion">The current SMAPI version.</param>
 public bool IsUpToDate(CachePaths paths, string hash, ISemanticVersion currentVersion)
 {
     return(hash == this.Hash &&
            this.ApiVersion == currentVersion.ToString() &&
            (!this.UseCachedAssembly || File.Exists(paths.Assembly)));
 }